Pile.c | Pile.s |
|
|
- 32 |
- 31 |
- 30 |
- 29 |
- 28 |
- 27 |
- 26 |
- 25 |
- 24 |
- 23 |
- 22 |
- 21 |
- 20 |
- 19 |
- 18 |
- 17 |
- 16 |
- 15 |
- 14 |
- 13 |
- 12 |
- 11 |
- 10 |
- 9 |
- 8 |
- 7 |
- 6 |
- 5 |
- 4 |
- 3 |
- 2 |
- 1 |
ebp |
mk | mj | mi | mh | mg | mf | me | md | mc | mb | ma |
Vous noterez que les variables globales a...k sont situées en dehors
de la pile, sur le tas statique.
La directive .comm réserve en effet de la
mémoire dans le segment de données et lui attribue un nom
(une étiquette).
Noter le trou
situé entre mg et mh, aux adresses ebp-12
et ebp-11.
C'est une optimisation faite par GCC pour aligner mh, mi, mj et mk sur des
mots-mémoire de 32 bits.
En effet, le 80386 a un bus de données de 32 bits. Il lit donc de
toute façon 32 bits simultanément.
Lire 32 bits à partir de l'adresse 18 demanderait donc deux
accès successifs : de 16 à 19, puis de 20 à 23.
Concernant la représentation des données en mémoire, le
programme mentionne trois constantes 12, -5, et 2.5.
12 se traduit naturellement par la valeur 12... soit 0000 0000 0000 0000 0000
0000 0000 11002.
-5 se traduit par 0xFFFB, soit 1111 1111 1111 10112. Ce qui donne,
en complément à 2 : compl(1..1 1011 - 1) = 0..0
01012 = 5.
2.5 se traduit par 0100 0000 0000 0100 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 00002. Soit :
Pile_.c | Pile_.s |
|
|
- 44 |
- 43 |
- 42 |
- 41 |
- 40 |
- 39 |
- 38 |
- 37 |
- 36 |
- 35 |
- 34 |
- 33 |
- 32 |
- 31 |
- 30 |
- 29 |
- 28 |
- 27 |
- 26 |
- 25 |
- 24 |
- 23 |
- 22 |
- 21 |
- 20 |
- 19 |
- 18 |
- 17 |
- 16 |
- 15 |
- 14 |
- 13 |
- 12 |
- 11 |
- 10 |
- 09 |
- 08 |
- 07 |
- 06 |
- 05 |
- 04 |
- 03 |
- 02 |
- 01 |
ebp |
mi | mk | md | mj | mc | mh | mb | mg | mf | me | ma |
On notait que GCC laisse un certain nombre de trous afin d'aligner les variables short sur des adresses paires, les variables int sur des adresses multiples de 4, et la variable double sur une adresse multiple de 8.
Ce comportement a changé avec les versions récentes de GCC. Il réordonne désormais les variables pour éviter un maximum de trous dans la pile...
Si l'on remplace les lignes int h
et int
mh
respectivement par static int h
et
static int mh
, on observe que la variable locale
mh devient globale, c'est à dire située hors de la
pile. Son nom devient alors mh.xyz
avec
xyz
un entier aléatoire.De cette façon,
sa valeur est conservée entre deux appels de la fonction.
Pile2.c | Pile2.s |
|
|
Sachant que le compilateur C lit le programme de haut en bas (dans ce sens),
il ne connait pas la fonction appel_fonction
au moment où elle
est appelée dans la fonction principale.
Il suppose donc que son format est correct, et crée le profil
correspondant. Il vérifiera alors les appels suivant en se basant sur
ce profil.
... | ||||||
adresse de retour | Fonction main2 |
|||||
EBPt-1 | EBPt-2(main) | |||||
b | ||||||
c | Fonction appel_fonction |
(Créé par main2) | ||||
adresse de retour (= ligne 13) |
||||||
EBPt | EBPt-1 | |||||
ESP | d |
On remarque que l'appel de la fonction main2 par la fonction main ne traduit pas le return
.
En effet, main2
stocke son résultat dans le registre eax.
main
doit également stocker son résultat dans le registre eax.
Il faudrait donc écrire movl %eax, %eax
, ce qui est totalement inutile.
Le compilateur retire donc cette instruction. Intelligent, non ?
$gcc -S -O3 pile2.c -o pile2.opt.s
Pile2.c | Pile2.opt.s |
|
|
On constate que la version optimisée de ce programme ne contient plus
les variables b
et d
. Le schéma mémoire se
réduit donc aux deux cadres de piles... vides !
Et encore, on peut noter que la fonction appel_fonction
n'est plus appelée du tout. GCC a fait ce que l'on nomme inlining
: Il a copié-collé le contenu de la fonction dans
main2
et dans main
.
La fonction main
se résume donc à remplir
la variable a
aléatoirement ( ici 0 ) et à
retourner 0.
Les fonctions ne sont conservées que parce qu'elles sont publiques.
Si un programme lie le fichier objet à d'autres, ces fonctions
doivent exister sous peine d'erreur d'édition des liens...
Si on rend la fonction privée static int
appel_fonction(int c)
, elle disparaît totalement du fichier
objet et de son source assembleur associé...