J'ai ce morceau de code en c:
int q = 10;
int s = 5;
int a[3];
printf("Address of a: %d\n", (int)a);
printf("Address of a[1]: %d\n", (int)&a[1]);
printf("Address of a[2]: %d\n", (int)&a[2]);
printf("Address of q: %d\n", (int)&q);
printf("Address of s: %d\n", (int)&s);
La sortie est:
Address of a: 2293584
Address of a[1]: 2293588
Address of a[2]: 2293592
Address of q: 2293612
Address of s: 2293608
Donc, je vois que de a
à a[2]
, les adresses mémoire augmentent de 4 octets chacune. Mais de q
à s
, les adresses mémoire diminuent de 4 octets.
Je me demande 2 choses:
- La pile augmente-t-elle ou diminue-t-elle? (Cela me ressemble aux deux dans ce cas)
- Que se passe-t-il entre les adresses mémoire
a[2]
etq
? Pourquoi y a-t-il une grande différence de mémoire là-bas? (20 octets).
Remarque: ce n'est pas une question de devoirs. Je suis curieux de savoir comment fonctionne la pile. Merci pour toute aide.
Réponses:
Le comportement de la pile (grandir ou diminuer) dépend de l'interface binaire de l'application (ABI) et de l'organisation de la pile d'appels (aka enregistrement d'activation).
Tout au long de sa vie, un programme est tenu de communiquer avec d'autres programmes comme OS. ABI détermine comment un programme peut communiquer avec un autre programme.
La pile pour différentes architectures peut croître dans les deux sens, mais pour une architecture, elle sera cohérente. Veuillez vérifier ce lien wiki. Mais la croissance de la pile est décidée par l'ABI de cette architecture.
Par exemple, si vous prenez l'ABI MIPS, la pile d'appels est définie comme ci-dessous.
Considérons que la fonction 'fn1' appelle 'fn2'. Maintenant, le cadre de pile tel que vu par 'fn2' est le suivant:
direction of | | growth of +---------------------------------+ stack | Parameters passed by fn1(caller)| from higher addr.| | to lower addr. | Direction of growth is opposite | | | to direction of stack growth | | +---------------------------------+ <-- SP on entry to fn2 | | Return address from fn2(callee) | V +---------------------------------+ | Callee saved registers being | | used in the callee function | +---------------------------------+ | Local variables of fn2 | |(Direction of growth of frame is | | same as direction of growth of | | stack) | +---------------------------------+ | Arguments to functions called | | by fn2 | +---------------------------------+ <- Current SP after stack frame is allocated
Vous pouvez maintenant voir que la pile se développe vers le bas. Ainsi, si les variables sont allouées à la trame locale de la fonction, les adresses de la variable croissent en fait vers le bas. Le compilateur peut décider de l'ordre des variables pour l'allocation de mémoire. (Dans votre cas, ce peut être «q» ou «s» qui est la première mémoire de pile allouée. Mais, généralement, le compilateur fait l'allocation de mémoire de pile selon l'ordre de la déclaration des variables).
Mais dans le cas des tableaux, l'allocation n'a qu'un seul pointeur et la mémoire à allouer sera en fait pointée par un seul pointeur. La mémoire doit être contiguë pour un tableau. Ainsi, bien que la pile augmente vers le bas, pour les tableaux, la pile grandit.
la source
C'est en fait deux questions. L'un concerne la manière dont la pile se développe lorsqu'une fonction en appelle une autre (lorsqu'une nouvelle image est allouée), et l'autre la disposition des variables dans le cadre d'une fonction particulière.
Ni l'un ni l'autre n'est spécifié par la norme C, mais les réponses sont un peu différentes:
f
pointeur de cadre sera-t-il supérieur ou inférieur aug
pointeur de cadre de? Cela peut aller dans les deux sens - cela dépend du compilateur et de l'architecture particuliers (recherchez "convention d'appel"), mais c'est toujours cohérent au sein d'une plate-forme donnée (à quelques exceptions près, voir les commentaires). Vers le bas est plus courant; c'est le cas des SPU x86, PowerPC, MIPS, SPARC, EE et Cell.la source
La direction dans laquelle les piles se développent est spécifique à l'architecture. Cela dit, je crois comprendre que seules quelques architectures matérielles ont des piles qui grandissent.
La direction dans laquelle une pile se développe est indépendante de la disposition d'un objet individuel. Ainsi, même si la pile peut grossir, les tableaux ne le seront pas (c'est-à-dire que & array [n] sera toujours <& array [n + 1]);
la source
Il n'y a rien dans la norme qui impose la façon dont les choses sont organisées sur la pile. En fait, vous pouvez créer un compilateur conforme qui ne stocke pas du tout les éléments de tableau dans les éléments contigus de la pile, à condition qu'il ait l'intelligence pour toujours faire correctement l'arithmétique des éléments de tableau (de sorte qu'il sache, par exemple, qu'un 1 était 1K de [0] et pourrait s'ajuster pour cela).
La raison pour laquelle vous pouvez obtenir des résultats différents est que, bien que la pile puisse s'agrandir pour y ajouter des "objets", le tableau est un seul "objet" et il peut avoir des éléments de tableau ascendants dans l'ordre opposé. Mais il n'est pas sûr de se fier à ce comportement car la direction peut changer et les variables peuvent être permutées pour diverses raisons, notamment, mais sans s'y limiter:
Voir ici mon excellent traité sur la direction de la pile :-)
En réponse à vos questions spécifiques:
Cela n'a pas d'importance du tout (en termes de standard) mais, comme vous l'avez demandé, cela peut augmenter ou diminuer en mémoire, selon l'implémentation.
Cela n'a pas d'importance du tout (en termes de norme). Voir ci-dessus pour les raisons possibles.
la source
Sur un x86, l '"allocation" mémoire d'un frame de pile consiste simplement à soustraire le nombre d'octets nécessaire au pointeur de pile (je crois que d'autres architectures sont similaires). En ce sens, je suppose que la pile grossit "vers le bas", en ce que les adresses deviennent progressivement plus petites au fur et à mesure que vous appelez plus profondément dans la pile (mais j'imagine toujours que la mémoire commence par 0 en haut à gauche et augmente les adresses lorsque vous vous déplacez à droite et envelopper, donc à mon image mentale la pile grandit ...). L'ordre des variables déclarées peut ne pas avoir d'incidence sur leurs adresses - je crois que la norme permet au compilateur de les réorganiser, tant que cela ne provoque pas d'effets secondaires (quelqu'un, veuillez me corriger si je me trompe) . Ils'
L'espace autour du tableau peut être une sorte de rembourrage, mais c'est mystérieux pour moi.
la source
Tout d'abord, ses 8 octets d'espace inutilisé en mémoire (ce n'est pas 12, rappelez-vous que la pile augmente vers le bas, donc l'espace qui n'est pas alloué est de 604 à 597). et pourquoi?. Parce que chaque type de données prend de l'espace en mémoire à partir de l'adresse divisible par sa taille. Dans notre cas, un tableau de 3 entiers prend 12 octets d'espace mémoire et 604 n'est pas divisible par 12. Il laisse donc des espaces vides jusqu'à ce qu'il rencontre une adresse mémoire divisible par 12, c'est 596.
Ainsi, l'espace mémoire alloué au tableau est de 596 à 584. Mais comme l'allocation du tableau se poursuit, le premier élément du tableau commence à partir de l'adresse 584 et non à partir de 596.
la source
Le compilateur est libre d'allouer des variables locales (auto) à n'importe quel endroit du cadre de la pile locale, vous ne pouvez pas déduire de manière fiable la direction de croissance de la pile uniquement à partir de cela. Vous pouvez déduire le sens de croissance de la pile en comparant les adresses des cadres de pile imbriqués, c'est-à-dire en comparant l'adresse d'une variable locale à l'intérieur du cadre de pile d'une fonction par rapport à son appel:
#include <stdio.h> int f(int *x) { int a; return x == NULL ? f(&a) : &a - x; } int main(void) { printf("stack grows %s!\n", f(NULL) < 0 ? "down" : "up"); return 0; }
la source
Je ne pense pas que ce soit déterministe comme ça. Le tableau a semble «croître» car cette mémoire doit être allouée de manière contiguë. Cependant, étant donné que q et s ne sont pas du tout liés l'un à l'autre, le compilateur colle simplement chacun d'eux dans un emplacement de mémoire libre arbitraire dans la pile, probablement ceux qui correspondent le mieux à une taille entière.
Ce qui s'est passé entre a [2] et q est que l'espace autour de l'emplacement de q n'était pas assez grand (c'est-à-dire qu'il n'était pas plus grand que 12 octets) pour allouer un tableau de 3 entiers.
la source
Ma pile semble s'étendre vers les adresses numérotées plus faibles.
Cela peut être différent sur un autre ordinateur, ou même sur mon propre ordinateur si j'utilise un autre appel de compilateur. ... ou le compilateur muigt choisit de ne pas utiliser du tout une pile (tout en ligne (fonctions et variables si je n'en ai pas pris l'adresse)).
$ cat stack.c #include <stdio.h> int stack(int x) { printf("level %d: x is at %p\n", x, (void*)&x); if (x == 0) return 0; return stack(x - 1); } int main(void) { stack(4); return 0; }
la source
La pile s'agrandit (sur x86). Cependant, la pile est allouée en un seul bloc lorsque la fonction se charge, et vous n'avez aucune garantie de l'ordre des éléments dans la pile.
Dans ce cas, il a alloué de l'espace pour deux entiers et un tableau trois int sur la pile. Il a également alloué 12 octets supplémentaires après le tableau, donc cela ressemble à ceci:
a [12 octets]
remplissage (?) [12 octets]
s [4 octets]
q [4 octets]
Pour une raison quelconque, votre compilateur a décidé qu'il devait allouer 32 octets pour cette fonction, et peut-être plus. C'est opaque pour vous en tant que programmeur C, vous ne savez pas pourquoi.
Si vous voulez savoir pourquoi, compilez le code en langage assembleur, je crois que c'est -S sur gcc et / S sur le compilateur C de MS. Si vous regardez les instructions d'ouverture de cette fonction, vous verrez l'ancien pointeur de pile en cours d'enregistrement, puis 32 (ou autre chose!) En être soustrait. À partir de là, vous pouvez voir comment le code accède à ce bloc de mémoire de 32 octets et comprendre ce que fait votre compilateur. À la fin de la fonction, vous pouvez voir le pointeur de pile en cours de restauration.
la source
Cela dépend de votre système d'exploitation et de votre compilateur.
la source
La pile grossit. Donc f (g (h ())), la pile allouée pour h commencera à une adresse inférieure, puis g et g seront inférieurs à f. Mais les variables au sein de la pile doivent suivre la spécification C,
http://c0x.coding-guidelines.com/6.5.8.html
1206 Si les objets pointés sont des membres du même objet agrégé, les pointeurs vers les membres de la structure déclarés plus tard comparent plus que les pointeurs aux membres déclarés plus tôt dans la structure, et les pointeurs vers les éléments du tableau avec des valeurs d'indice plus grandes comparent plus que les pointeurs aux éléments du même tableau avec des valeurs d'indice inférieures.
& a [0] <& a [1], doit toujours être vrai, quelle que soit la façon dont 'a' est alloué
la source
croît vers le bas et cela est dû au petit standard d'ordre des octets endian en ce qui concerne l'ensemble de données en mémoire.
Une façon de voir les choses est que la pile grandit vers le haut si vous regardez la mémoire de 0 à partir du haut et max à partir du bas.
La raison de la croissance de la pile vers le bas est de pouvoir déréférencer du point de vue de la pile ou du pointeur de base.
N'oubliez pas que le déréférencement de tout type augmente de l'adresse la plus basse à la plus élevée. Étant donné que la pile croît vers le bas (adresse la plus élevée à la plus basse), cela vous permet de traiter la pile comme une mémoire dynamique.
C'est l'une des raisons pour lesquelles tant de langages de programmation et de script utilisent une machine virtuelle basée sur la pile plutôt que sur un registre.
la source
The reason for the stack growing downward is to be able to dereference from the perspective of the stack or base pointer.
Très beau raisonnementCela dépend de l'architecture. Pour vérifier votre propre système, utilisez ce code de GeeksForGeeks :
// C program to check whether stack grows // downward or upward. #include<stdio.h> void fun(int *main_local_addr) { int fun_local; if (main_local_addr < &fun_local) printf("Stack grows upward\n"); else printf("Stack grows downward\n"); } int main() { // fun's local variable int main_local; fun(&main_local); return 0; }
la source