Question

Certaines conditions peuvent provoquer des débordements de pile sur un système Linux x86:

  • struct my_big_object [HUGE_NUMBER] sur la pile. La parcourir provoque finalement SIGSEGV .
  • La routine alloca () (comme malloc () ), mais utilise la pile, se libère automatiquement et explose également avec SIGSEGV si c'est trop gros). Mise à jour: alloca () n’est pas officiellement obsolète comme je l’ai indiqué à l’origine; il est simplement découragé .

Existe-t-il un moyen de détecter par programme si la pile locale est suffisamment grande pour un objet donné? Je sais que la taille de la pile est ajustable via ulimit , et j’espère donc qu’il existe un moyen (même si cela n’est pas portable). Idéalement, j'aimerais pouvoir faire quelque chose comme ceci:

int min_stack_space_available = /* ??? */;
if (object_size < min_stack_space_available)
{
    char *foo = alloca(object_size);
    do_stuff(foo);
}
else
{
    char *foo = malloc(object_size);
    do_stuff(foo);
    free(foo);
}
Était-ce utile?

La solution

Vous pouvez déterminer l'espace de pile disponible dans le processus en recherchant la taille de l'espace de pile d'un processus, puis en soustrayant la quantité utilisée.

ulimit -s

affiche la taille de la pile sur un système Linux. Pour une approche programmatique, consultez getrlimit () . Ensuite, pour déterminer la profondeur actuelle de la pile, soustrayez un pointeur situé en haut de la pile. Par exemple (code non testé):

unsigned char *bottom_of_stack_ptr;

void call_function(int argc, char *argv) {
    unsigned char top_of_stack;
    unsigned int depth = (&top_of_stack > bottom_of_stack_ptr) ? 
        &top_of_stack-bottom_of_stack_ptr : 
        bottom_of_stack_ptr-&top_of_stack;

    if( depth+100 < PROGRAMMATICALLY_DETERMINED_STACK_SIZE ) {
        ...
    }
}

int main(int argc, char *argv) {
    unsigned char bottom_of_stack;
    bottom_of_stack_ptr = &bottom_of_stack;
    my_function();
    return 0;
}

Autres conseils

  

La routine obsolète alloca () (comme malloc (), mais utilise la pile,   se libère automatiquement et explose également avec SIGSEGV s’il est trop gros).

Pourquoi alloca est-il déconseillé?

Quoi qu'il en soit, dans votre cas, alloca vs malloc est-il plus rapide? (Cela en vaut-il la peine?)

Et n'obtenez-vous pas le droit de retour de alloca s'il ne reste plus assez d'espace? (de la même manière que malloc?)

Et quand votre code plante, où se trouve-t-il? est-ce dans alloca ou dans doStuff ()?

/ Johan

Je ne sais pas si cela s'applique sous Linux, mais sous Windows, il est possible de rencontrer des violations d'accès avec des allocations de pile importantes même si elles réussissent!

Cela est dû au fait que, par défaut, VMM de Windows ne marque en réalité que les quelques premières (pas exactement combien) exactement de pages de 4096 octets de RAM de pile sont paginables (c.-à-d. sauvegardées par le fichier d'échange), car il croit que marchez du haut vers le bas; à mesure que les accès se rapprochent de la "limite" actuelle, les pages inférieures et inférieures sont marquées comme étant paginables. Mais cela signifie qu'une lecture / écriture précoce en mémoire, bien en dessous du sommet de la pile, déclenchera une violation d'accès car cette mémoire n'est pas encore allouée!

alloca () va renvoyer NULL en cas d'échec, je pense que le comportement de alloca (0) est indéfini et varie selon la plate-forme. Si vous vérifiez cela avant do_something (), vous ne devriez jamais être touché par un SEGV.

J'ai quelques questions à vous poser:

  1. Pourquoi, oh pourquoi, avez-vous besoin de quelque chose d'aussi gros sur la pile? La taille par défaut sur la plupart des systèmes est 8M, c'est encore trop petit?
  2. Si la fonction appelant alloca () bloque, protéger le même nombre de segments de mémoire via mlock () / mlockall () garantit une performance d'accès identique (c.-à-d. "Ne m'échangez pas, petit!"). temps? Si vous utilisez un planificateur "agressif" plus agressif, il est recommandé de les appeler quand même.

La question est intéressante mais soulève un sourcil. Il soulève l'aiguille sur mon carré-piquet-rond-trou-o-mètre.

Vous ne dites pas grand-chose de la raison pour laquelle vous voulez allouer sur la pile, mais si c'est le modèle de mémoire de pile qui est attrayant, vous pouvez également mettre en œuvre l'allocation de pile sur le tas. Allouez une grande quantité de mémoire au début du programme et conservez une pile de pointeurs correspondant aux images de la pile normale. N'oubliez pas de faire apparaître votre pointeur de pile privé lorsque la fonction est renvoyée.

Plusieurs compilateurs, par exemple Ouvrez Watcom C / C ++ , prennent en charge la fonction stackavail () qui vous permet de faire exactement cela

Vous pouvez utiliser GNU libsigsegv pour traiter une erreur de page, y compris en cas de débordement de pile (à partir de son site Web):

  

Dans certaines applications, le gestionnaire de débordement de la pile effectue un nettoyage ou informe l'utilisateur, puis met immédiatement fin à l'application. Dans d'autres applications, le gestionnaire de débordement de pile longjmps revient à un point central de l'application. Cette bibliothèque prend en charge les deux utilisations. Dans le second cas, le gestionnaire doit s’assurer de restaurer le masque de signal normal (car de nombreux signaux sont bloqués pendant l’exécution du gestionnaire) et doit également appeler sigsegv_leave_handler () pour transférer le contrôle; alors seulement il peut être longjmp.

La fonction alloca n'est pas déconseillée. Cependant, ce n'est pas dans POSIX et cela dépend aussi de la machine et du compilateur. La page de manuel Linux pour alloca indique que "pour certaines applications, son utilisation peut améliorer l'efficacité par rapport à l'utilisation de malloc et, dans certains cas, simplifier la désaffectation de mémoire dans les applications qui utilisent longjmp () ou siglongjmp (). Sinon, son utilisation est déconseillée. "

La page de manuel indique également qu'il n'y a aucune indication d'erreur si le cadre de la pile ne peut pas être étendu. Toutefois, après une attribution ayant échoué, le programme recevra probablement un signal SIGSEGV. "

La performance de malloc a en fait été mentionnée dans le podcast Stackoverflow n ° 36 .

(Je sais que ce n'est pas une réponse appropriée à votre question, mais je pensais que cela pourrait être utile de toute façon.)

Même s'il ne s'agit pas d'une réponse directe à votre question, j'espère que vous êtes au courant de l'existence de valgrind - un outil formidable pour détecter de tels problèmes en exécution, sous Linux.

En ce qui concerne le problème de la pile, vous pouvez tenter d’allouer des objets de manière dynamique à partir d’un pool fixe détectant ces débordements. Avec une macro-magie simple, vous pouvez exécuter cette tâche au moment du débogage, avec du code réel exécuté au moment de la publication, et ainsi savoir (du moins pour les scénarios que vous exécutez) que vous n'en prenez pas trop. Voici plus d'informations et un lien à un exemple d'implémentation.

Il n'y a pas de bonne façon de penser. Peut-être est-il possible d’utiliser getrlimit () (suggéré précédemment) et une arithmétique de pointeur? Mais demandez-vous d’abord si vous le souhaitez vraiment.

void *closeToBase;

main () {
  int closeToBase;
  stackTop = &closeToBase;
}

int stackHasRoomFor(int bytes) {
  int currentTop;
  return getrlimit(...) - (¤tTop  - closeToBase) > bytes + SomeExtra;
}

Personnellement, je ne ferais pas cela. Allouez de grosses dépenses sur le tas, la pile n’était pas faite pour ça.

La fin de la zone de pile est déterminée dynamiquement par le système d'exploitation. Bien que vous puissiez trouver le " statique " les limites de la pile en examinant les zones de mémoire virtuelle (VMA) de manière très dépendante du système d’exploitation (voir les fichiers stackvma * dans libsigsegv / src / ), vous devrez également prendre en compte

Veuillez nous excuser si cela énonce une évidence, mais vous pouvez facilement écrire une fonction pour tester une taille d’allocation de pile spécifique en essayant simplement la méthode alloca (de cette taille) et en interceptant une exception de dépassement de capacité de la pile. Si vous le souhaitez, vous pouvez l'insérer dans une fonction, avec des calculs prédéterminés pour la surcharge de la pile de fonctions. Par exemple:

bool CanFitOnStack( size_t num_bytes )
{
    int stack_offset_for_function = 4; // <- Determine this
    try
    {
        alloca( num_bytes - stack_offset_for_function );
    }
    catch ( ... )
    {
        return false;
    }

    return true;
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top