2010-10-31 23 views
0

Je suis intéressé par la disposition d'une allocation de mémoire dynamique et exécutable en utilisant la pile et comment le processeur et le noyau gèrent ensemble la région de pile, comme pendant les appels de fonction et autres scénarios de utilisant l'allocation de mémoire basée sur la pile. De même, le débordement de pile et d'autres dangers associés à ce modèle sont les autres conceptions d'exécution de code qui ne sont pas basées sur une pile et ne présentent pas de tels problèmes. Une vidéo ou une animation serait d'une grande aide.Où trouver des informations détaillées sur le fonctionnement de la pile dans les processeurs x86

Répondre

1

Typiquement (n'importe quel processeur, pas seulement x86) il y a un espace d'adressage de RAM, et typiquement le programme est dans la mémoire inférieure et croît vers le haut pendant que vous exécutez. Supposons que votre programme soit 0x1000 octets et qu'il soit chargé à 0x0000 alors vous faites un malloc de 0x3000 octets. L'adresse retournée sera 0x1000 dans cette situation hypothétique et maintenant les 0x4000 octets inférieurs sont activement utilisés par le programme. Les mallocs supplémentaires continuent de croître de cette manière. Free() s ne fait pas nécessairement baisser cette consommation, cela dépend de la façon dont la mémoire est gérée et du mélange de programmes de malloc() s et de free() s.

La pile va normalement de haut en bas. Say 0x10000 est l'adresse à partir de laquelle le pointeur de la pile commence. Supposons que vous ayez une fonction avec trois variables int non signées de 32 bits, et qu'aucun paramètre ne soit passé, vous avez besoin de trois emplacements de pile pour contenir ces variables (en supposant qu'aucune optimisation n'a réduit cette exigence). par 3 * 4 = 12 octets, donc le pointeur de pile est changé en 0xFFF4, une de vos variables est à l'adresse 0xFFF4 + 0 une à 0xFFF4 + 4 et la troisième à 0xFFF4 + 8. Si cette fonction appelle une autre fonction, le pointeur de la pile continue de se déplacer vers zéro en mémoire. Et pendant que vous continuez à malloc() votre mémoire de programme utilisée grandit vers le haut. Non cochée, ils vont entrer en collision, et le code nécessaire pour faire cette vérification est assez prohibitif pour être rarement utilisé. C'est pourquoi les variables locales sont bonnes pour l'optimisation et quelques autres choses mais mauvaises car la consommation de la pile est souvent non-déterministe ou du moins l'analyse n'est pas faite par le programmeur moyen.

Sur les ISA (architectures d'ensembles d'instructions) comme x86 où il y a un nombre limité de registres utilisables, les fonctions doivent souvent également transmettre des arguments sur la pile. Les règles régissant où et comment les choses sont passées et renvoyées sont définies et bien comprises par le compilateur, ce n'est pas une chose aléatoire. Quoi qu'il en soit, en plus de laisser de la place aux variables locales, certains des arguments de la fonction sont sur la pile et parfois la valeur de retour est sur la pile. En particulier avec x86, chaque appel de fonction provoque la croissance de la pile vers le bas, et les fonctions d'appel des fonctions aggravent cette situation. Pensez à ce que la récursivité peut faire à votre pile.

Quelles sont vos alternatives? Utilisez un jeu d'instructions avec plus de registres avec une fonction d'appel de fonction qui utilise plus de registres et moins de pile. Utilisez moins d'arguments lors de l'appel de fonctions. Utilisez moins de variables locales. Malloc moins. Utilisez un bon compilateur avec un bon optimiseur ainsi que d'aider l'optimiseur en utilisant facile d'optimiser les habitudes lors du codage. De manière réaliste, pour avoir un processeur générique utile pour lequel vous écrivez des programmes génériques utiles, vous devez avoir une pile et la possibilité que la pile déborde et/ou entre en collision avec le tas. Maintenant, le modèle de mémoire segmentée du x86 ainsi que le mmus en général vous permettent de conserver la mémoire du programme et de les empiler bien loin l'un de l'autre. Des mécanismes de protection peuvent également être utilisés si le tas ou la pile s'aventure en dehors de leur espace alloué, une erreur de protection se produit. Encore un oubli par le programmeur mais il est plus facile de savoir ce qui s'est passé et de le déboguer que les effets secondaires aléatoires qui se produisent lorsque la pile se réduit dans l'espace mémoire du programme. L'utilisation d'un mécanisme de protection comme celui-ci est une solution beaucoup plus facile pour aider le programmeur à contrôler la croissance de la pile que de construire quelque chose dans le code généré par le compilateur pour vérifier la collision sur chaque appel de fonction et malloc.

Un autre écueil qui est souvent demandé dans les entrevues d'emploi est quelque chose le long des lignes de:

 
int * myfun (int a) 
{ 
    int i; 

    i=a+7; 
    return(&i); 
} 

Cela peut prendre de nombreuses formes, la chose à comprendre est que la variable i est temporairement allouée sur la pile et seulement alloué pendant que la fonction est en cours d'exécution, lorsque la fonction renvoie le pointeur de pile libère la mémoire allouée pour i et la fonction suivante appelée peut très bien écraser cette mémoire. Donc, en retournant l'adresse à une variable stockée sur la pile est une mauvaise idée. Code qui fait quelque chose comme ça peut fonctionner correctement pendant des semaines, des mois, des années avant d'être détecté.

Maintenant, ceci est acceptable même sur un processeur basé sur une pile (le zylin zpu par exemple).

 
int myfun (int a) 
{ 
    int i; 

    i=a+7; 
    return(i); 
} 

en partie parce qu'il ny a pas beaucoup que vous pouvez faire autre que d'utiliser globals (oui ce cas spécifique ne nécessite pas la variable supplémentaire i, mais supposons que votre code est assez compliqué que vous avez besoin que variable de retour local), la deuxième est parce qu'en C, le code appelant libère sa partie de la pile. Signification sur un x86 par exemple si vous appelez une fonction avec deux paramètres sur la pile, disons deux octets de 4 octets, le code appelant déplace le pointeur de pile de 8 et place ces deux paramètres dans cette mémoire (sp + 0 et sp + 4), puis lorsque la fonction renvoie le code appelant est celui qui désalloue ces deux variables en ajoutant 8 au pointeur de la pile. Donc, dans le code ci-dessus en utilisant i et en renvoyant i par valeur, la convention d'appel C pour ce processeur sait où trouver la valeur de retour, et une fois cette valeur capturée, la mémoire de pile contenant cette valeur n'est plus nécessaire. Ma compréhension est que pascal, par exemple borland turbo pascal par exemple le calee nettoyé la pile. Ainsi, l'appelant mettrait les deux variables sur la pile et la fonction appelée nettoierait la pile. Ce n'est pas une mauvaise idée en ce qui concerne la gestion de la pile, vous pouvez imbriquer beaucoup plus profond de cette façon. il y a des avantages et des inconvénients aux deux approches.

+0

hmmm, vous avez seulement une étiquette d'assemblage, ai-je complètement mal compris votre question? –