Gestion de la mémoire en C |
Le présent document est publié sous la licence « Verbatim Copying » telle que définie par la Free Software Fondation dans le cadre du projet GNU.
Toute copie ou diffusion de ce document dans son intégralité est permise (et même encouragée), quel qu'en soit le support à la seule condition que cette notice, inclant les copyrights, soit préservée.
Copyright (C) 2002, 2003, 2004 Yann LANGLAIS, ilay.
La plus récente version en date de ce document est sise à l'url http://ilay.org/yann/articles/mem/.
Toute remarque, suggestion ou correction est bienvenue et peut être adressée à Yann LANGLAIS, ilay.org.
Le langage C est souvent considéré comme un langage difficile et peu fiable. La seconde assertion n'est pas acceptable dans la mesure où la plupart des applications, y compris les L4G et RAD sont eux mêmes écrits en C.
En fait, ces idées reçues proviennent principalement de l'utilisation intensive des pointeurs. Si le C est souvent mal considéré, c'est bien souvent à cause de la méconnaissance du concept de pointeur et de la gestion de la mémoire qui lui est associé.
Le présent document se propose de présenter ces concepts depuis leurs fondements théoriques jusqu'à leur application pratique.
Il ne s'agit pas pour autant de définir les différentes notions avec la plus grande rigueur qui soit. Mon propos n'est pas de faire de l'initiation à l'assembleur, ni à l'électronique. Mon but est de présenter la gestion de la mémoire sous un angle différent de celui généralement adopté.
L'audience visée est principalement celle des développeurs ayant déjà été initiés au langage C.
La mémoire de l'ordinateur est assimilable, en première approximation
à un long tableau continu. La largeur de ce tableau s'exprime en "bits".
En général, les cette taille comporte des subdivisions. En effet, pour une
taille de 32bits, par exemple, un octet n'etant codé que sur 8bits, il est
nécessaire, afin de ne pas perdre trop d'espace, de pouvoir stocker 4 octets
différents par case de 32 bits et donc accéder de manière indépendante à ces 4
subdivisions.
Un bus 32bits est en général découpé en subdivisions de 8 et 16 bits.
Chaque subdivision (octet) est numérotée. Cette numérotation correspond à une adresse mémoire.
Cette dernière vision des choses ne s'applique pas à la réalité virtuelle d'un processus. En effet, il est fréquent de voir des processus utiliser plus de mémoire que la quantité de RAM physique réellement disponible. Dans ce dernier cas, l'application utilise une partie de la mémoire RAM, mais aussi d'autres formes telle que le swap.
Le système et les différents composants matériels (en particulier la "Memory Management Unit", MMU ou unité de gestion de la mémoire) se chargent d'agréger les différentes ressources (RAM, swap, disque dur, ...) et de faire croire aux processus que la mémoire est un vaste tableau homogène et contigu.
Les mécanismes rendant possible cette illusion sont assez complexes dépassent de loin le cadre de ce document, mais ce sujet est largement couvert1. Il est cependant nécessaire de noter que les cases du "tableau-mémoire" ne sont pas réllement présentes (i.e. pointent réellement vers des zones de mémoire physique réelle) que lorsque celles-ci sont réellement utilisées.
Pour chaque processus, cette zone contigüe s'étale de
0x00000000 à
0xFFFFFFFF.
La zone inférieure à la zone de chargement du segment text du programme est réservée et
ne peut être accédée en écriture.
La zone supérieure à la valeur de la macro
PAGE_OFFSET
est réservée pour le noyau (mapping noyau des zones de mémoire du processus).
Cet espace mémoire est rempli selon certaines règles dépendantes des sytèmes et des architectures matérielles.
Tout d'abord la zone de "programme", découpée en plusieurs segments dont :
Ensuite, vient le tas (heap). C'est la mémoire allouée dynamiquement avec les fonctions de type malloc(). La croissance de cette zone s'effectue vers les adresses hautes. L'adresse de base du tas est donnée par la fonction sbrk(0) avant toute allocation ou par brk(0).
Vient la zone de mémoire libre", ou mémoire non allouée. Elle est
consommée par le tas.
La réservation d'une zone suppémentaire se fait avec la fonction sbrk(taillesupp) ou
taillesup est la taille de la zone à réserver.
Après cette zone de mémoire libre, une partie de la mémoire est réservée pour les bibliothèques dynamiques (links), chargées soit lors du chargement de l'exécutable quand les bibliothèques ont été référencées lors de la phase d'édition de liens (symboles nécessaires à la compilation), soit pendant l'exéction du programme et par le programme lui même par l'appel aux fonctions dlopen() et dlsym() (programmation de greffons, chargement de modules ...).
Enfin, la dernière zone de l'espace utilisateur est la pile. La pile commence,
à l'inverse des autres zones, à l'adresse la plus haute (soit théoriquement
PAGE_OFFSET-1). La pile croit donc vers les
adresses basses.
La pile se compose de boites ou cadres (frames). Chacune de ces boites correspont à une
procédure ou fonction. La fonction main() sera
la boite 1 (seconde sur la pile). Si main()
appelle une fonction "toto()", alors, lorsque
le programme passera dans toto(), une boite
numérotée 2 sera placée sur la pile, au "dessus" de la boite numérotée 1 de
main().
A la sortie de la fonction toto(), la boite numéro 2 sera détruite (dépilée/modification
de la topographie ou mapping de la mémoire virtelle).
Chaque boite contient les variables locales à la procédure (i.e. définie sur la pile),
l'adresse de retour de la procédure, une sauvegarde des paramères passée en entrée à la
procédure, voire aussi une copie de tout ou partie de la zone de registres du microprocesseur.
Une boite, ou frame, n'est donc valide que tant que l'on ne sort pas de la fonction à
laquelle elle est associée.
Il n'est donc possible d'utiliser un pointeur sur une zone de mémoire réservée sur la
pile que dans la fonction où cette mémoire est réservée et les sous fonctions appelées
par cette fonction.
Des la sortie d'une fonction, la zone la frame associée à cette fonction est éliminée
de la carte de la mémoire virtuelle. La zone occupée par l'ancienne frame se retrouve
donc contenir des données aléatoires.
En corrolaires :
La pile est réservée statiquement. Il est impossible de changer dynamiquement sa taille.
Une pile est alloué localement :
La pile représente la proportion de la mémoire de travail utilisée temporairement pour un traitement particulier.
% cat frameshow.c #includeint function3(int a, int b, int c, int d) { int i_3 = 0x30303030; printf("in function3\n"); return 0x3F3F3F3F; } int function2(int a, int b, int c, int d) { int i_2 = 0x20202020; printf("in function2\n"); function3(0xCCCCCCC1, 0xCCCCCCC2, 0xCCCCCCC3, 0xCCCCCCC4); return 0x2F2F2F2F; } int function1(int a, int b, int c, int d) { int i_1 = 0x10101010; printf("in function1\n"); function2(0xBBBBBBB1, 0xBBBBBBB2, 0xBBBBBBB3, 0xBBBBBBB4); return 0x1F1F1F1F; } int final(int a, int b) { int i_final = 0x0FF00FF0; return i_final; } int main() { int i_main = 0x12345678; printf("in main\n"); function1(0xAAAAAAA1, 0xAAAAAAA2, 0xAAAAAAA3, 0xAAAAAAA4); final(0xDDDDDDD1, 0xDDDDDDD2); return 0; } % cc -g frameshow.c -o frameshow % dis frameshow **** DISASSEMBLER **** disassembly for frameshow section .text _start() 106b8: bc 10 20 00 clr %fp 106bc: e0 03 a0 40 ld [%sp + 64], %l0 106c0: 13 00 00 83 sethi %hi(0x20c00), %o1 106c4: e0 22 60 54 st %l0, [%o1 + 0x54] 106c8: a2 03 a0 44 add %sp, 68, %l1 106cc: 13 00 00 83 sethi %hi(0x20c00), %o1 106d0: e2 22 60 50 st %l1, [%o1 + 0x50] 106d4: 13 00 00 83 sethi %hi(0x20c00), %o1 106d8: e2 22 60 44 st %l1, [%o1 + 0x44] 106dc: a5 2c 20 02 sll %l0, 2, %l2 106e0: a4 04 a0 04 add %l2, 4, %l2 106e4: a4 04 40 12 add %l1, %l2, %l2 106e8: 27 00 00 83 sethi %hi(0x20c00), %l3 106ec: e8 04 e0 38 ld [%l3 + 0x38], %l4 106f0: 80 a5 20 00 cmp %l4, 0 106f4: 12 80 00 03 bne 0x10700 106f8: 01 00 00 00 nop 106fc: e4 24 e0 38 st %l2, [%l3 + 56] 10700: 2b 00 00 00 sethi %hi(__fsr_init_value), %l5 10704: aa 15 60 00 or %l5, __fsr_init_value, %l5 ! __fsr_init_value 10708: 80 90 00 15 orcc %g0, %l5, %g0 1070c: 02 80 00 12 be 0x10754 10710: 01 00 00 00 nop 10714: ad 2d 60 02 sll %l5, 2, %l6 10718: ae 0d a3 00 and %l6, 768, %l7 1071c: ac 0d 60 3f and %l5, 63, %l6 10720: ac 15 80 17 or %l6, %l7, %l6 10724: ab 2d a0 16 sll %l6, 22, %l5 10728: 29 00 00 83 sethi %hi(0x20c00), %l4 1072c: a8 15 20 3c or %l4, 0x3c, %l4 ! __crt_scratch 10730: c1 2d 00 00 st %fsr, [%l4] 10734: ec 05 00 00 ld [%l4], %l6 10738: ae 20 00 17 sub %g0, %l7, %l7 1073c: af 3d e0 1f sra %l7, 31, %l7 10740: af 2d e0 1e sll %l7, 30, %l7 10744: ac 2d 80 17 andn %l6, %l7, %l6 10748: ac 15 80 15 or %l6, %l5, %l6 1074c: ec 25 00 00 st %l6, [%l4] 10750: c1 0d 00 00 ld [%l4], %fsr 10754: 9c 23 a0 20 sub %sp, 32, %sp 10758: 80 90 00 01 orcc %g0, %g1, %g0 1075c: 02 80 00 04 be 0x1076c 10760: 90 10 00 01 mov %g1, %o0 10764: 40 00 40 f1 call atexit 10768: 01 00 00 00 nop 1076c: 11 00 00 42 sethi %hi(0x10800), %o0 10770: 40 00 40 ee call atexit 10774: 90 12 22 7c or %o0, 636, %o0 10778: 11 00 00 83 sethi %hi(0x20c00), %o0 1077c: d0 02 20 48 ld [%o0 + 0x48], %o0 10780: 80 90 00 08 orcc %g0, %o0, %g0 10784: 12 80 00 09 bne 0x107a8 10788: 01 00 00 00 nop 1078c: 11 00 00 83 sethi %hi(0x20c00), %o0 10790: d0 02 20 4c ld [%o0 + 0x4c], %o0 10794: 80 90 00 08 orcc %g0, %o0, %g0 10798: 02 80 00 04 be 0x107a8 1079c: 01 00 00 00 nop 107a0: 40 00 40 e2 call atexit 107a4: 01 00 00 00 nop 107a8: 40 00 00 b2 call _init 107ac: 01 00 00 00 nop 107b0: 90 10 00 10 mov %l0, %o0 107b4: 92 10 00 11 mov %l1, %o1 107b8: 94 10 00 12 mov %l2, %o2 107bc: 96 10 00 13 mov %l3, %o3 107c0: 40 00 00 88 call main 107c4: 01 00 00 00 nop 107c8: 40 00 40 db call exit 107cc: 01 00 00 00 nop 107d0: 40 00 40 dc call _exit 107d4: 01 00 00 00 nop 107d8: 00 01 00 00 unimp 0x10000 107dc: 00 01 00 00 unimp 0x10000 107e0: 00 01 00 00 unimp 0x10000 107e4: 00 01 00 00 unimp 0x10000 function3() 107e8: 9d e3 bf 98 save %sp, -104, %sp 107ec: f6 27 a0 50 st %i3, [%fp + 80] 107f0: f4 27 a0 4c st %i2, [%fp + 76] 107f4: f2 27 a0 48 st %i1, [%fp + 72] 107f8: f0 27 a0 44 st %i0, [%fp + 68] 107fc: 21 0c 0c 0c sethi %hi(0x30303000), %l0 10800: a0 14 20 30 or %l0, 0x30, %l0 ! 0x30303030 10804: e0 27 bf f8 st %l0, [%fp - 8] 10808: 21 00 00 42 sethi %hi(0x10800), %l0 1080c: a0 14 22 b8 or %l0, 0x2b8, %l0 ! 0x10ab8 10810: 40 00 40 cf call printf 10814: 90 14 00 00 or %l0, %g0, %o0 10818: 21 0f cf cf sethi %hi(0x3f3f3c00), %l0 1081c: a0 14 23 3f or %l0, 0x33f, %l0 ! 0x3f3f3f3f 10820: 10 80 00 04 ba 0x10830 10824: e0 27 bf fc st %l0, [%fp - 4] 10828: 10 80 00 02 ba 0x10830 1082c: 01 00 00 00 nop 10830: e0 07 bf fc ld [%fp - 4], %l0 10834: b0 14 00 00 or %l0, %g0, %i0 10838: 81 c7 e0 08 ret 1083c: 81 e8 00 00 restore 10840: 00 01 00 00 unimp 0x10000 10844: 00 01 00 00 unimp 0x10000 10848: 00 01 00 00 unimp 0x10000 1084c: 00 01 00 00 unimp 0x10000 function2() 10850: 9d e3 bf 98 save %sp, -104, %sp 10854: f6 27 a0 50 st %i3, [%fp + 80] 10858: f4 27 a0 4c st %i2, [%fp + 76] 1085c: f2 27 a0 48 st %i1, [%fp + 72] 10860: f0 27 a0 44 st %i0, [%fp + 68] 10864: 21 08 08 08 sethi %hi(0x20202000), %l0 10868: a0 14 20 20 or %l0, 0x20, %l0 ! 0x20202020 1086c: e0 27 bf f8 st %l0, [%fp - 8] 10870: 21 00 00 42 sethi %hi(0x10800), %l0 10874: a0 14 22 c8 or %l0, 0x2c8, %l0 ! 0x10ac8 10878: 40 00 40 b5 call printf 1087c: 90 14 00 00 or %l0, %g0, %o0 10880: 21 0c cc cc sethi %hi(0x33333000), %l0 10884: a0 1c 3c c1 xor %l0, -831, %l0 10888: 23 0c cc cc sethi %hi(0x33333000), %l1 1088c: a2 1c 7c c2 xor %l1, -830, %l1 10890: 25 0c cc cc sethi %hi(0x33333000), %l2 10894: a4 1c bc c3 xor %l2, -829, %l2 10898: 27 0c cc cc sethi %hi(0x33333000), %l3 1089c: a6 1c fc c4 xor %l3, -828, %l3 108a0: 90 14 00 00 or %l0, %g0, %o0 108a4: 92 14 40 00 or %l1, %g0, %o1 108a8: 94 14 80 00 or %l2, %g0, %o2 108ac: 7f ff ff cf call function3 108b0: 96 14 c0 00 or %l3, %g0, %o3 108b4: 21 0b cb cb sethi %hi(0x2f2f2c00), %l0 108b8: a0 14 23 2f or %l0, 0x32f, %l0 ! 0x2f2f2f2f 108bc: 10 80 00 04 ba 0x108cc 108c0: e0 27 bf fc st %l0, [%fp - 4] 108c4: 10 80 00 02 ba 0x108cc 108c8: 01 00 00 00 nop 108cc: e0 07 bf fc ld [%fp - 4], %l0 108d0: b0 14 00 00 or %l0, %g0, %i0 108d4: 81 c7 e0 08 ret 108d8: 81 e8 00 00 restore 108dc: 00 01 00 00 unimp 0x10000 108e0: 00 01 00 00 unimp 0x10000 108e4: 00 01 00 00 unimp 0x10000 108e8: 00 01 00 00 unimp 0x10000 108ec: 00 01 00 00 unimp 0x10000 function1() 108f0: 9d e3 bf 98 save %sp, -104, %sp 108f4: f6 27 a0 50 st %i3, [%fp + 80] 108f8: f4 27 a0 4c st %i2, [%fp + 76] 108fc: f2 27 a0 48 st %i1, [%fp + 72] 10900: f0 27 a0 44 st %i0, [%fp + 68] 10904: 21 04 04 04 sethi %hi(0x10101000), %l0 10908: a0 14 20 10 or %l0, 0x10, %l0 ! 0x10101010 1090c: e0 27 bf f8 st %l0, [%fp - 8] 10910: 21 00 00 42 sethi %hi(0x10800), %l0 10914: a0 14 22 d8 or %l0, 0x2d8, %l0 ! 0x10ad8 10918: 40 00 40 8d call printf 1091c: 90 14 00 00 or %l0, %g0, %o0 10920: 21 11 11 11 sethi %hi(0x44444400), %l0 10924: a0 1c 3f b1 xor %l0, -79, %l0 10928: 23 11 11 11 sethi %hi(0x44444400), %l1 1092c: a2 1c 7f b2 xor %l1, -78, %l1 10930: 25 11 11 11 sethi %hi(0x44444400), %l2 10934: a4 1c bf b3 xor %l2, -77, %l2 10938: 27 11 11 11 sethi %hi(0x44444400), %l3 1093c: a6 1c ff b4 xor %l3, -76, %l3 10940: 90 14 00 00 or %l0, %g0, %o0 10944: 92 14 40 00 or %l1, %g0, %o1 10948: 94 14 80 00 or %l2, %g0, %o2 1094c: 7f ff ff c1 call function2 10950: 96 14 c0 00 or %l3, %g0, %o3 10954: 21 07 c7 c7 sethi %hi(0x1f1f1c00), %l0 10958: a0 14 23 1f or %l0, 0x31f, %l0 ! 0x1f1f1f1f 1095c: 10 80 00 04 ba 0x1096c 10960: e0 27 bf fc st %l0, [%fp - 4] 10964: 10 80 00 02 ba 0x1096c 10968: 01 00 00 00 nop 1096c: e0 07 bf fc ld [%fp - 4], %l0 10970: b0 14 00 00 or %l0, %g0, %i0 10974: 81 c7 e0 08 ret 10978: 81 e8 00 00 restore 1097c: 00 01 00 00 unimp 0x10000 10980: 00 01 00 00 unimp 0x10000 10984: 00 01 00 00 unimp 0x10000 10988: 00 01 00 00 unimp 0x10000 1098c: 00 01 00 00 unimp 0x10000 final() 10990: 9d e3 bf 98 save %sp, -104, %sp 10994: f2 27 a0 48 st %i1, [%fp + 72] 10998: f0 27 a0 44 st %i0, [%fp + 68] 1099c: 21 03 fc 03 sethi %hi(0xff00c00), %l0 109a0: a0 14 23 f0 or %l0, 0x3f0, %l0 ! 0xff00ff0 109a4: e0 27 bf f8 st %l0, [%fp - 8] 109a8: e0 07 bf f8 ld [%fp - 8], %l0 109ac: 10 80 00 04 ba 0x109bc 109b0: e0 27 bf fc st %l0, [%fp - 4] 109b4: 10 80 00 02 ba 0x109bc 109b8: 01 00 00 00 nop 109bc: e0 07 bf fc ld [%fp - 4], %l0 109c0: b0 14 00 00 or %l0, %g0, %i0 109c4: 81 c7 e0 08 ret 109c8: 81 e8 00 00 restore 109cc: 00 01 00 00 unimp 0x10000 109d0: 00 01 00 00 unimp 0x10000 109d4: 00 01 00 00 unimp 0x10000 109d8: 00 01 00 00 unimp 0x10000 109dc: 00 01 00 00 unimp 0x10000 main() 109e0: 9d e3 bf 98 save %sp, -104, %sp 109e4: 21 04 8d 15 sethi %hi(0x12345400), %l0 109e8: a0 14 22 78 or %l0, 0x278, %l0 ! 0x12345678 109ec: e0 27 bf f8 st %l0, [%fp - 8] 109f0: 21 00 00 42 sethi %hi(0x10800), %l0 109f4: a0 14 22 e8 or %l0, 0x2e8, %l0 ! 0x10ae8 109f8: 40 00 40 55 call printf 109fc: 90 14 00 00 or %l0, %g0, %o0 10a00: 21 15 55 55 sethi %hi(0x55555400), %l0 10a04: a0 1c 3e a1 xor %l0, -351, %l0 10a08: 23 15 55 55 sethi %hi(0x55555400), %l1 10a0c: a2 1c 7e a2 xor %l1, -350, %l1 10a10: 25 15 55 55 sethi %hi(0x55555400), %l2 10a14: a4 1c be a3 xor %l2, -349, %l2 10a18: 27 15 55 55 sethi %hi(0x55555400), %l3 10a1c: a6 1c fe a4 xor %l3, -348, %l3 10a20: 90 14 00 00 or %l0, %g0, %o0 10a24: 92 14 40 00 or %l1, %g0, %o1 10a28: 94 14 80 00 or %l2, %g0, %o2 10a2c: 7f ff ff b1 call function1 10a30: 96 14 c0 00 or %l3, %g0, %o3 10a34: 21 08 88 88 sethi %hi(0x22222000), %l0 10a38: a0 1c 3d d1 xor %l0, -559, %l0 10a3c: 23 08 88 88 sethi %hi(0x22222000), %l1 10a40: a2 1c 7d d2 xor %l1, -558, %l1 10a44: 90 14 00 00 or %l0, %g0, %o0 10a48: 7f ff ff d2 call final 10a4c: 92 14 40 00 or %l1, %g0, %o1 10a50: 10 80 00 04 ba 0x10a60 10a54: c0 27 bf fc st %g0, [%fp - 4] 10a58: 10 80 00 02 ba 0x10a60 10a5c: 01 00 00 00 nop 10a60: e0 07 bf fc ld [%fp - 4], %l0 10a64: b0 14 00 00 or %l0, %g0, %i0 10a68: 81 c7 e0 08 ret 10a6c: 81 e8 00 00 restore section .init _init() 10a70: 9d e3 bf a0 save %sp, -96, %sp 10a74: 81 c7 e0 08 ret 10a78: 81 e8 00 00 restore section .fini _fini() 10a7c: 9d e3 bf a0 save %sp, -96, %sp 10a80: 81 c7 e0 08 ret 10a84: 81 e8 00 00 restore % dbx frameshow Reading frameshow Reading ld.so.1 Reading libc.so.1 Reading libdl.so.1 Reading libc_psr.so.1 dbx> alias P="print -f0x%x" dbx> print main main = &main() at 0x109e0 dbx> print function1 function1 = &function1(int a, int b, int c, int d) at 0x108f0 dbx> print function2 function2 = &function2(int a, int b, int c, int d) at 0x10850 dbx> print function3 function3 = &function3(int a, int b, int c, int d) at 0x107e8 dbx> stop in main (2) stop in main dbx> run Running: frameshow (process id 28873) stopped in main at line 31 in file "frameshow.c" 31 int i_main = 0x12345678; dbx> next stopped in main at line 32 in file "frameshow.c" 32 printf("in main\n"); dbx> P $sp $sp = 0xffbef3e0 dbx> P $fp $fp = 0xffbef448 dbx> ex $sp, $fp 0xffbef3e0: 0x12345678 0x00000000 0x00000000 0x00000000 0xffbef3f0: 0x00000000 0x00000000 0x00000000 0xff3dc890 0xffbef400: 0x00000001 0xffbef4ac 0xffbef4b4 0x00020c00 0xffbef410: 0x00000000 0x00000000 0xffbef448 0x000107c0 0xffbef420: 0x00000000 0x00000000 0x00000003 0xffbef4ac 0xffbef430: 0x00000004 0xffbef4b4 0x00000005 0xffbef5fc 0xffbef440: 0x12345678 0x00000000 0x00000001 dbx> regs current frame: [1] g0-g3 0x00000000 0xff319c04 0x00000000 0x00000000 g4-g7 0x00000000 0x00000000 0x00000000 0x00000000 o0-o3 0x00000000 0xff3420d8 0xff33e5d8 0xffffffff o4-o7 0x000222e8 0xff29bc20 0xffbef3e0 0xff29bc20 l0-l3 0x12345678 0x00000000 0x00000000 0x00000000 l4-l7 0x00000000 0x00000000 0x00000000 0xff3dc890 i0-i3 0x00000001 0xffbef4ac 0xffbef4b4 0x00020c00 i4-i7 0x00000000 0x00000000 0xffbef448 0x000107c0 y 0x00000000 ccr 0x00000000 pc 0x000109f0:main+0x10 sethi %hi(0x10800), %l0 npc 0x000109f4:main+0x14 or %l0, 0x2e8, %l0 dbx> next stopped in main at line 33 in file "frameshow.c" 33 function1(0xAAAAAAA1, 0xAAAAAAA2, 0xAAAAAAA3, 0xAAAAAAA4); dbx> step stopped in function1 at line 18 in file "frameshow.c" 18 int i_1 = 0x10101010; dbx> next stopped in function1 at line 19 in file "frameshow.c" 19 printf("in function1\n"); dbx> next stopped in function1 at line 20 in file "frameshow.c" 20 function2(0xBBBBBBB1, 0xBBBBBBB2, 0xBBBBBBB3, 0xBBBBBBB4); dbx> P $sp $sp = 0xffbef378 dbx> P $fp $fp = 0xffbef3e0 dbx> ex $sp, 0xffbef448 0xffbef378: 0x00010ad8 0x00000000 0x00000000 0x00000000 0xffbef388: 0x00000000 0x00000000 0x00000000 0x00000000 0xffbef398: 0xaaaaaaa1 0xaaaaaaa2 0xaaaaaaa3 0xaaaaaaa4 input parameters 0xffbef3a8: 0x000222e8 0xff29bc20 0xffbef3e0 0x00010a2c previous stack end and return address in main 0xffbef3b8: 0xffbef3e0 0x000109f8 0x00000000 0x00000000 0xffbef3c8: 0x00000000 0x00000000 0x00000000 0xff29bc20 0xffbef3d8: 0x10101010 0x000109f8 0xaaaaaaa1 0xaaaaaaa2 parameters passed 0xffbef3e8: 0xaaaaaaa3 0xaaaaaaa4 0x00000000 0x00000000 function1 0xffbef3f8: 0x00000000 0xff3dc890 0x00000001 0xffbef4ac 0xffbef408: 0xffbef4b4 0x00020c00 0x00000000 0x00000000 0xffbef418: 0xffbef448 0x000107c0 0x00000000 0xaaaaaaa1 0xffbef428: 0xaaaaaaa2 0xaaaaaaa3 0xaaaaaaa4 0x000222e8 0xffbef438: 0xff29bc20 0xffbef5fc 0x12345678 0x00000000 value of i_main on stack 0xffbef448: 0x00000001 dbx> regs current frame: [1] g0-g3 0x00000000 0x0000000a 0x00000000 0x00000000 g4-g7 0x00000000 0x00000000 0x00000000 0x00000000 o0-o3 0x0000000d 0xff340284 0xff343a54 0x00000000 o4-o7 0x00000000 0x00000000 0xffbef378 0x00010918 l0-l3 0x00010ad8 0x00000000 0x00000000 0x00000000 l4-l7 0x00000000 0x00000000 0x00000000 0x00000000 i0-i3 0xaaaaaaa1 0xaaaaaaa2 0xaaaaaaa3 0xaaaaaaa4 i4-i7 0x000222e8 0xff29bc20 0xffbef3e0 0x00010a2c y 0x00000000 ccr 0x00000099 pc 0x00010920:function1+0x30 sethi %hi(0x44444400), %l0 npc 0x00010924:function1+0x34 xor %l0, -0x4f, %l0 dbx> step stopped in function2 at line 11 in file "frameshow.c" 11 int i_2 = 0x20202020; dbx> next stopped in function2 at line 12 in file "frameshow.c" 12 printf("in function2\n"); dbx> next stopped in function2 at line 13 in file "frameshow.c" 13 function3(0xCCCCCCC1, 0xCCCCCCC2, 0xCCCCCCC3, 0xCCCCCCC4); dbx> P $sp $sp = 0xffbef310 dbx> P $fp $fp = 0xffbef378 dbx> ex $sp, 0xffbef448 0xffbef310: 0x00010ac8 0x00000000 0x00000000 0x00000000 0xffbef320: 0x00000000 0x00000000 0x00000000 0x00000000 0xffbef330: 0xbbbbbbb1 0xbbbbbbb2 0xbbbbbbb3 0xbbbbbbb4 0xffbef340: 0x00000000 0x00000000 0xffbef378 0x0001094c 0xffbef350: 0xffbef5fc 0x00000000 0x00000000 0x00000000 0xffbef360: 0x00000000 0x00000000 0x00000000 0x00000000 0xffbef370: 0x20202020 0x00000000 0xbbbbbbb1 0xbbbbbbb2 0xffbef380: 0xbbbbbbb3 0xbbbbbbb4 0x00000000 0x00000000 0xffbef390: 0x00000000 0x00000000 0xaaaaaaa1 0xaaaaaaa2 0xffbef3a0: 0xaaaaaaa3 0xaaaaaaa4 0x000222e8 0xff29bc20 0xffbef3b0: 0xffbef3e0 0x00010a2c 0xffbef3e0 0xbbbbbbb1 0xffbef3c0: 0xbbbbbbb2 0xbbbbbbb3 0xbbbbbbb4 0x00000000 0xffbef3d0: 0x00000000 0xff29bc20 0x10101010 0x000109f8 0xffbef3e0: 0xaaaaaaa1 0xaaaaaaa2 0xaaaaaaa3 0xaaaaaaa4 0xffbef3f0: 0x00000000 0x00000000 0x00000000 0xff3dc890 0xffbef400: 0x00000001 0xffbef4ac 0xffbef4b4 0x00020c00 0xffbef410: 0x00000000 0x00000000 0xffbef448 0x000107c0 0xffbef420: 0x00000000 0xaaaaaaa1 0xaaaaaaa2 0xaaaaaaa3 0xffbef430: 0xaaaaaaa4 0x000222e8 0xff29bc20 0xffbef5fc 0xffbef440: 0x12345678 0x00000000 0x00000001 dbx> regs current frame: [1] g0-g3 0x00000000 0x0000000a 0x00000000 0x00000000 g4-g7 0x00000000 0x00000000 0x00000000 0x00000000 o0-o3 0x0000000d 0xff340284 0xff343a54 0x00000000 o4-o7 0x00000000 0x00000000 0xffbef310 0x00010878 l0-l3 0x00010ac8 0x00000000 0x00000000 0x00000000 l4-l7 0x00000000 0x00000000 0x00000000 0x00000000 i0-i3 0xbbbbbbb1 0xbbbbbbb2 0xbbbbbbb3 0xbbbbbbb4 i4-i7 0x00000000 0x00000000 0xffbef378 0x0001094c y 0x00000000 ccr 0x00000099 pc 0x00010880:function2+0x30 sethi %hi(0x33333000), %l0 npc 0x00010884:function2+0x34 xor %l0, -0x33f, %l0 dbx> step stopped in function3 at line 5 in file "frameshow.c" 5 int i_3 = 0x30303030; dbx> next stopped in function3 at line 6 in file "frameshow.c" 6 printf("in function3\n"); dbx> next stopped in function3 at line 7 in file "frameshow.c" 7 return 0x3F3F3F3F; dbx> P $sp $sp = 0xffbef2a8 dbx> P $fp $fp = 0xffbef310 dbx> ex $sp, 0xffbef448 0xffbef2a8: 0x00010ab8 0x00000000 0x00000000 0x00000000 0xffbef2b8: 0x00000000 0x00000000 0x00000000 0x00000000 0xffbef2c8: 0xccccccc1 0xccccccc2 0xccccccc3 0xccccccc4 0xffbef2d8: 0x00000000 0x00000000 0xffbef310 0x000108ac 0xffbef2e8: 0x00000000 0x00000000 0x00000000 0x00000000 0xffbef2f8: 0x00000000 0x00000000 0x00000000 0x00000000 0xffbef308: 0x30303030 0x00000000 0xccccccc1 0xccccccc2 0xffbef318: 0xccccccc3 0xccccccc4 0x00000000 0x00000000 0xffbef328: 0x00000000 0x00000000 0xbbbbbbb1 0xbbbbbbb2 0xffbef338: 0xbbbbbbb3 0xbbbbbbb4 0x00000000 0x00000000 0xffbef348: 0xffbef378 0x0001094c 0xffbef5fc 0xccccccc1 0xffbef358: 0xccccccc2 0xccccccc3 0xccccccc4 0x00000000 0xffbef368: 0x00000000 0x00000000 0x20202020 0x00000000 0xffbef378: 0xbbbbbbb1 0xbbbbbbb2 0xbbbbbbb3 0xbbbbbbb4 0xffbef388: 0x00000000 0x00000000 0x00000000 0x00000000 0xffbef398: 0xaaaaaaa1 0xaaaaaaa2 0xaaaaaaa3 0xaaaaaaa4 0xffbef3a8: 0x000222e8 0xff29bc20 0xffbef3e0 0x00010a2c 0xffbef3b8: 0xffbef3e0 0xbbbbbbb1 0xbbbbbbb2 0xbbbbbbb3 0xffbef3c8: 0xbbbbbbb4 0x00000000 0x00000000 0xff29bc20 0xffbef3d8: 0x10101010 0x000109f8 0xaaaaaaa1 0xaaaaaaa2 0xffbef3e8: 0xaaaaaaa3 0xaaaaaaa4 0x00000000 0x00000000 0xffbef3f8: 0x00000000 0xff3dc890 0x00000001 0xffbef4ac 0xffbef408: 0xffbef4b4 0x00020c00 0x00000000 0x00000000 0xffbef418: 0xffbef448 0x000107c0 0x00000000 0xaaaaaaa1 0xffbef428: 0xaaaaaaa2 0xaaaaaaa3 0xaaaaaaa4 0x000222e8 0xffbef438: 0xff29bc20 0xffbef5fc 0x12345678 0x00000000 0xffbef448: 0x00000001 dbx> regs current frame: [1] g0-g3 0x00000000 0x0000000a 0x00000000 0x00000000 g4-g7 0x00000000 0x00000000 0x00000000 0x00000000 o0-o3 0x0000000d 0xff340284 0xff343a54 0x00000000 o4-o7 0x00000000 0x00000000 0xffbef2a8 0x00010810 l0-l3 0x00010ab8 0x00000000 0x00000000 0x00000000 l4-l7 0x00000000 0x00000000 0x00000000 0x00000000 i0-i3 0xccccccc1 0xccccccc2 0xccccccc3 0xccccccc4 i4-i7 0x00000000 0x00000000 0xffbef310 0x000108ac y 0x00000000 ccr 0x00000099 pc 0x00010818:function3+0x30 sethi %hi(0x3f3f3c00), %l0 npc 0x0001081c:function3+0x34 or %l0, 0x33f, %l0 dbx> next stopped in function3 at line 8 in file "frameshow.c" 8 } dbx> next stopped in function2 at line 14 in file "frameshow.c" 14 return 0x2F2F2F2F; dbx> P $sp $sp = 0xffbef310 dbx> P $fp $fp = 0xffbef378 dbx> ex 0xffbef2a8, 0xffbef448 0xffbef2a8: 0x3f3f3f3f 0x00000000 0x00000000 0x00000000 0xffbef2b8: 0x00000000 0x00000000 0x00000000 0x00000000 0xffbef2c8: 0x3f3f3f3f 0xccccccc2 0xccccccc3 0xccccccc4 0xffbef2d8: 0x00000000 0x00000000 0xffbef310 0x000108ac 0xffbef2e8: 0x00000000 0x00000000 0x00000000 0x00000000 0xffbef2f8: 0x00000000 0x00000000 0x00000000 0x00000000 0xffbef308: 0x30303030 0x3f3f3f3f 0xccccccc1 0xccccccc2 0xffbef318: 0xccccccc3 0xccccccc4 0x00000000 0x00000000 0xffbef328: 0x00000000 0x00000000 0xbbbbbbb1 0xbbbbbbb2 0xffbef338: 0xbbbbbbb3 0xbbbbbbb4 0x00000000 0x00000000 0xffbef348: 0xffbef378 0x0001094c 0xffbef5fc 0xccccccc1 0xffbef358: 0xccccccc2 0xccccccc3 0xccccccc4 0x00000000 0xffbef368: 0x00000000 0x00000000 0x20202020 0x00000000 0xffbef378: 0xbbbbbbb1 0xbbbbbbb2 0xbbbbbbb3 0xbbbbbbb4 0xffbef388: 0x00000000 0x00000000 0x00000000 0x00000000 0xffbef398: 0xaaaaaaa1 0xaaaaaaa2 0xaaaaaaa3 0xaaaaaaa4 0xffbef3a8: 0x000222e8 0xff29bc20 0xffbef3e0 0x00010a2c 0xffbef3b8: 0xffbef3e0 0xbbbbbbb1 0xbbbbbbb2 0xbbbbbbb3 0xffbef3c8: 0xbbbbbbb4 0x00000000 0x00000000 0xff29bc20 0xffbef3d8: 0x10101010 0x000109f8 0xaaaaaaa1 0xaaaaaaa2 0xffbef3e8: 0xaaaaaaa3 0xaaaaaaa4 0x00000000 0x00000000 0xffbef3f8: 0x00000000 0xff3dc890 0x00000001 0xffbef4ac 0xffbef408: 0xffbef4b4 0x00020c00 0x00000000 0x00000000 0xffbef418: 0xffbef448 0x000107c0 0x00000000 0xaaaaaaa1 0xffbef428: 0xaaaaaaa2 0xaaaaaaa3 0xaaaaaaa4 0x000222e8 0xffbef438: 0xff29bc20 0xffbef5fc 0x12345678 0x00000000 0xffbef448: 0x00000001 dbx> regs current frame: [1] g0-g3 0x00000000 0x0000000a 0x00000000 0x00000000 g4-g7 0x00000000 0x00000000 0x00000000 0x00000000 o0-o3 0x3f3f3f3f 0xccccccc2 0xccccccc3 0xccccccc4 o4-o7 0x00000000 0x00000000 0xffbef310 0x000108ac l0-l3 0xccccccc1 0xccccccc2 0xccccccc3 0xccccccc4 l4-l7 0x00000000 0x00000000 0x00000000 0x00000000 i0-i3 0xbbbbbbb1 0xbbbbbbb2 0xbbbbbbb3 0xbbbbbbb4 i4-i7 0x00000000 0x00000000 0xffbef378 0x0001094c y 0x00000000 ccr 0x00000099 pc 0x000108b4:function2+0x64 sethi %hi(0x2f2f2c00), %l0 npc 0x000108b8:function2+0x68 or %l0, 0x32f, %l0 dbx> next stopped in function2 at line 15 in file "frameshow.c" 15 } dbx> next stopped in function1 at line 21 in file "frameshow.c" 21 return 0x1F1F1F1F; dbx> P $sp $sp = 0xffbef378 dbx> P $fp $fp = 0xffbef3e0 dbx> ex 0xffbef2a8, 0xffbef448 0xffbef2a8: 0x3f3f3f3f 0x00000000 0x00000000 0x00000000 0xffbef2b8: 0x00000000 0x00000000 0x00000000 0x00000000 0xffbef2c8: 0x3f3f3f3f 0xccccccc2 0xccccccc3 0xccccccc4 0xffbef2d8: 0x00000000 0x00000000 0xffbef310 0x000108ac 0xffbef2e8: 0x00000000 0x00000000 0x00000000 0x00000000 0xffbef2f8: 0x00000000 0x00000000 0x00000000 0x00000000 0xffbef308: 0x30303030 0x3f3f3f3f 0x2f2f2f2f 0xccccccc2 0xffbef318: 0xccccccc3 0xccccccc4 0x00000000 0x00000000 0xffbef328: 0x00000000 0x00000000 0x2f2f2f2f 0xbbbbbbb2 0xffbef338: 0xbbbbbbb3 0xbbbbbbb4 0x00000000 0x00000000 0xffbef348: 0xffbef378 0x0001094c 0xffbef5fc 0xccccccc1 0xffbef358: 0xccccccc2 0xccccccc3 0xccccccc4 0x00000000 0xffbef368: 0x00000000 0x00000000 0x20202020 0x2f2f2f2f 0xffbef378: 0xbbbbbbb1 0xbbbbbbb2 0xbbbbbbb3 0xbbbbbbb4 0xffbef388: 0x00000000 0x00000000 0x00000000 0x00000000 0xffbef398: 0xaaaaaaa1 0xaaaaaaa2 0xaaaaaaa3 0xaaaaaaa4 0xffbef3a8: 0x000222e8 0xff29bc20 0xffbef3e0 0x00010a2c 0xffbef3b8: 0xffbef3e0 0xbbbbbbb1 0xbbbbbbb2 0xbbbbbbb3 0xffbef3c8: 0xbbbbbbb4 0x00000000 0x00000000 0xff29bc20 0xffbef3d8: 0x10101010 0x000109f8 0xaaaaaaa1 0xaaaaaaa2 0xffbef3e8: 0xaaaaaaa3 0xaaaaaaa4 0x00000000 0x00000000 0xffbef3f8: 0x00000000 0xff3dc890 0x00000001 0xffbef4ac 0xffbef408: 0xffbef4b4 0x00020c00 0x00000000 0x00000000 0xffbef418: 0xffbef448 0x000107c0 0x00000000 0xaaaaaaa1 0xffbef428: 0xaaaaaaa2 0xaaaaaaa3 0xaaaaaaa4 0x000222e8 0xffbef438: 0xff29bc20 0xffbef5fc 0x12345678 0x00000000 0xffbef448: 0x00000001 dbx> regs current frame: [1] g0-g3 0x00000000 0x0000000a 0x00000000 0x00000000 g4-g7 0x00000000 0x00000000 0x00000000 0x00000000 o0-o3 0x2f2f2f2f 0xbbbbbbb2 0xbbbbbbb3 0xbbbbbbb4 o4-o7 0x00000000 0x00000000 0xffbef378 0x0001094c l0-l3 0xbbbbbbb1 0xbbbbbbb2 0xbbbbbbb3 0xbbbbbbb4 l4-l7 0x00000000 0x00000000 0x00000000 0x00000000 i0-i3 0xaaaaaaa1 0xaaaaaaa2 0xaaaaaaa3 0xaaaaaaa4 i4-i7 0x000222e8 0xff29bc20 0xffbef3e0 0x00010a2c y 0x00000000 ccr 0x00000099 pc 0x00010954:function1+0x64 sethi %hi(0x1f1f1c00), %l0 npc 0x00010958:function1+0x68 or %l0, 0x31f, %l0 dbx> next stopped in function1 at line 22 in file "frameshow.c" 22 } dbx> next stopped in main at line 34 in file "frameshow.c" 34 final(0xDDDDDDD1, 0xDDDDDDD2); dbx> step stopped in final at line 25 in file "frameshow.c" 25 int i_final = 0x0FF00FF0; dbx> next stopped in final at line 26 in file "frameshow.c" 26 return i_final; dbx> P $sp $sp = 0xffbef378 dbx> P $fp $fp = 0xffbef3e0 dbx> ex 0xffbef2a8, 0xffbef448 0xffbef2a8: 0x3f3f3f3f 0x00000000 0x00000000 0x00000000 0xffbef2b8: 0x00000000 0x00000000 0x00000000 0x00000000 0xffbef2c8: 0x3f3f3f3f 0xccccccc2 0xccccccc3 0xccccccc4 0xffbef2d8: 0x00000000 0x00000000 0xffbef310 0x000108ac 0xffbef2e8: 0x00000000 0x00000000 0x00000000 0x00000000 0xffbef2f8: 0x00000000 0x00000000 0x00000000 0x00000000 0xffbef308: 0x30303030 0x3f3f3f3f 0x2f2f2f2f 0xccccccc2 0xffbef318: 0xccccccc3 0xccccccc4 0x00000000 0x00000000 0xffbef328: 0x00000000 0x00000000 0x2f2f2f2f 0xbbbbbbb2 0xffbef338: 0xbbbbbbb3 0xbbbbbbb4 0x00000000 0x00000000 0xffbef348: 0xffbef378 0x0001094c 0xffbef5fc 0xccccccc1 0xffbef358: 0xccccccc2 0xccccccc3 0xccccccc4 0x00000000 0xffbef368: 0x00000000 0x00000000 0x20202020 0x2f2f2f2f 0xffbef378: 0x0ff00ff0 0x00000000 0x00000000 0x00000000 0xffbef388: 0x00000000 0x00000000 0x00000000 0x00000000 0xffbef398: 0xddddddd1 0xddddddd2 0xaaaaaaa3 0xaaaaaaa4 0xffbef3a8: 0x000222e8 0xff29bc20 0xffbef3e0 0x00010a48 0xffbef3b8: 0xffbef3e0 0xbbbbbbb1 0xbbbbbbb2 0xbbbbbbb3 0xffbef3c8: 0xbbbbbbb4 0x00000000 0x00000000 0xff29bc20 0xffbef3d8: 0x0ff00ff0 0x1f1f1f1f 0xddddddd1 0xddddddd2 0xffbef3e8: 0xaaaaaaa3 0xaaaaaaa4 0x00000000 0x00000000 0xffbef3f8: 0x00000000 0xff3dc890 0x00000001 0xffbef4ac 0xffbef408: 0xffbef4b4 0x00020c00 0x00000000 0x00000000 0xffbef418: 0xffbef448 0x000107c0 0x00000000 0xddddddd1 0xffbef428: 0xddddddd2 0xaaaaaaa3 0xaaaaaaa4 0x000222e8 0xffbef438: 0xff29bc20 0x00000000 0x12345678 0x00010764 0xffbef448: 0x00000001 dbx> regs current frame: [1] g0-g3 0x00000000 0x0000000a 0x00000000 0x00000000 g4-g7 0x00000000 0x00000000 0x00000000 0x00000000 o0-o3 0x00000000 0x00000000 0x00000000 0x00000000 o4-o7 0x00000000 0x00000000 0xffbef378 0x00000000 l0-l3 0x0ff00ff0 0x00000000 0x00000000 0x00000000 l4-l7 0x00000000 0x00000000 0x00000000 0x00000000 i0-i3 0xddddddd1 0xddddddd2 0xaaaaaaa3 0xaaaaaaa4 i4-i7 0x000222e8 0xff29bc20 0xffbef3e0 0x00010a48 y 0x00000000 ccr 0x00000099 pc 0x000109a8:final+0x18 ld [%fp - 0x8], %l0 npc 0x000109ac:final+0x1c ba final+0x2c dbx> next stopped in final at line 27 in file "frameshow.c" 27 } dbx> next stopped in main at line 35 in file "frameshow.c" 35 return 0; dbx> P $sp $sp = 0xffbef3e0 dbx> P $fp $fp = 0xffbef448 dbx> ex 0xffbef2a8, 0xffbef448 0xffbef2a8: 0x3f3f3f3f 0x00000000 0x00000000 0x00000000 0xffbef2b8: 0x00000000 0x00000000 0x00000000 0x00000000 0xffbef2c8: 0x3f3f3f3f 0xccccccc2 0xccccccc3 0xccccccc4 0xffbef2d8: 0x00000000 0x00000000 0xffbef310 0x000108ac 0xffbef2e8: 0x00000000 0x00000000 0x00000000 0x00000000 0xffbef2f8: 0x00000000 0x00000000 0x00000000 0x00000000 0xffbef308: 0x30303030 0x3f3f3f3f 0x2f2f2f2f 0xccccccc2 0xffbef318: 0xccccccc3 0xccccccc4 0x00000000 0x00000000 0xffbef328: 0x00000000 0x00000000 0x2f2f2f2f 0xbbbbbbb2 0xffbef338: 0xbbbbbbb3 0xbbbbbbb4 0x00000000 0x00000000 0xffbef348: 0xffbef378 0x0001094c 0xffbef5fc 0xccccccc1 0xffbef358: 0xccccccc2 0xccccccc3 0xccccccc4 0x00000000 0xffbef368: 0x00000000 0x00000000 0x20202020 0x2f2f2f2f 0xffbef378: 0x0ff00ff0 0x00000000 0x00000000 0x00000000 0xffbef388: 0x00000000 0x00000000 0x00000000 0x00000000 0xffbef398: 0x0ff00ff0 0xddddddd2 0xaaaaaaa3 0xaaaaaaa4 0xffbef3a8: 0x000222e8 0xff29bc20 0xffbef3e0 0x00010a48 0xffbef3b8: 0xffbef3e0 0xbbbbbbb1 0xbbbbbbb2 0xbbbbbbb3 0xffbef3c8: 0xbbbbbbb4 0x00000000 0x00000000 0xff29bc20 0xffbef3d8: 0x0ff00ff0 0x0ff00ff0 0xddddddd1 0xddddddd2 0xffbef3e8: 0xaaaaaaa3 0xaaaaaaa4 0x00000000 0x00000000 0xffbef3f8: 0x00000000 0xff3dc890 0x00000001 0xffbef4ac 0xffbef408: 0xffbef4b4 0x00020c00 0x00000000 0x00000000 0xffbef418: 0xffbef448 0x000107c0 0x00000000 0xddddddd1 0xffbef428: 0xddddddd2 0xaaaaaaa3 0xaaaaaaa4 0x000222e8 0xffbef438: 0xff29bc20 0x00000000 0x12345678 0x00010764 0xffbef448: 0x00000001 dbx> regs current frame: [1] g0-g3 0x00000000 0x0000000a 0x00000000 0x00000000 g4-g7 0x00000000 0x00000000 0x00000000 0x00000000 o0-o3 0x0ff00ff0 0xddddddd2 0xaaaaaaa3 0xaaaaaaa4 o4-o7 0x000222e8 0xff29bc20 0xffbef3e0 0x00010a48 l0-l3 0xddddddd1 0xddddddd2 0xaaaaaaa3 0xaaaaaaa4 l4-l7 0x00000000 0x00000000 0x00000000 0xff3dc890 i0-i3 0x00000001 0xffbef4ac 0xffbef4b4 0x00020c00 i4-i7 0x00000000 0x00000000 0xffbef448 0x000107c0 y 0x00000000 ccr 0x00000099 pc 0x00010a50:main+0x70 ba main+0x80 npc 0x00010a54:main+0x74 st %g0, [%fp - 0x4]
Un programme C gère la mémoire de trois façons différentes:
Exemple : #include <stdio.h> #include <stdlib.h> static int i_stat = 4; /* Stocké dans le segment data */ int i_glob; /* Stocké dans le segment bss */ int *pi_pg; /* Stocké dans le segment bss */ /* main est stocké dans le segment text de la zone de programme */ int main(int nargs, char **args) { /* paramètres nargs et args stockés dans la frame numéro 1 de la pile */ int *pi_loc; /* dans la frame 1 de la pile */ void *sbrk0; /* nécessaire pour stocker l'adresse de base avant le premier malloc */ sbrk0 = (void *) sbrk(0); if (!(pi_loc = (int *) malloc(sizeof(int) * 16))) /* réservation de 16 x sizeof(int) sur le tas */ return 1; if (!(pi_pg = (int *) malloc(sizeof(int) * 8))) { /* réservation de 8 x sizeof(int) sur le tas */ free(pi_loc); return 2; } printf("adresse de i_stat = 0x%08x (zone programme, segment data)\n", &i_stat); printf("adresse de i_glob = 0x%08x (zone programme, segment bss)\n", &i_glob); printf("adresse de pi_pg = 0x%08x (zone programme, segment bss)\n", &pi_pg); printf("adresse de main = 0x%08x (zone programme, segment text)\n", main); printf("adresse de nargs = 0x%08x (pile frame 1)\n", &nargs); printf("adresse de args = 0x%08x (pile frame 1)\n", &args); printf("adresse de pi_loc = 0x%08x (pile frame 1)\n", &pi_loc); printf("sbrk(0) (heap) = 0x%08x (tas)\n", sbrk0); printf("pi_loc = 0x%08x (tas)\n", pi_loc); printf("pi_pg = 0x%08x (tas)\n", pi_pg); free(pi_pg); free(pi_loc); return 0; } Donne sous Sparc/Solaris : adresse de i_stat = 0x00020c70 (zone programme, segment data) adresse de i_glob = 0x00020ca4 (zone programme, segment bss) adresse de pi_pg = 0x00020ca8 (zone programme, segment bss) adresse de main = 0x0001068c (zone programme, segment text) adresse de nargs = 0xffbeefa4 (pile frame 1) adresse de args = 0xffbeefa8 (pile frame 1) adresse de pi_loc = 0xffbeef4c (pile frame 1) sbrk(0) (heap) = 0x00020c00 (tas) pi_loc = 0x00020cb8 (tas) pi_pg = 0x00020d08 (tas) Parallèlement, l'utilitaire elfdump donne : index value size type bind oth ver shndx name [17] 0x00020c68 0x00000000 SECT LOCL D 0 .data [21] 0x00020c88 0x00000000 SECT LOCL D 0 .bss [47] 0x00020c70 0x00000004 OBJT LOCL D 0 .data i_stat [60] 0x00020ca4 0x00000004 OBJT GLOB D 0 .bss i_glob [68] 0x0001068c 0x000001e0 FUNC GLOB D 0 .text main [80] 0x00020ca8 0x00000004 OBJT GLOB D 0 .bss pi_pg |
On notera que la variables pi_pg est stockée dans le segment bss mais que les données
pointées par la variable sont elles stockées dans le tas.
De même, la variable pi_loc est stockée sur la pile, mais les données vers lesquelles elle pointe sont stockées sur le tas. |
A retenir :
|
Un allocateur mémoire est un ensemble de routine responsable de la gestion du tas.
Bien qu'un processus pense avoir toute la mémoire pour lui, il faut néanmoins s'assurer d'une part que cette mémoire est réellement disponible (et ne s'envolera pas toute seule lors de modifications des pages mémoires par la mmu), et d'autre part que les variables d'une fonction ne soit pas écrasées par une celles d'une autre.
Par ailleurs, les différentes sous parties d'un programme ne nécessitent pas de conserver toute la mémoire allouée pendant toute la durée de l'exécution. Par exemple, lorsque l'on referme un fichier, il est préférable libérer toute la mémoire qui lui correspond. Cet espace se verra de nouveau disponible pour ouvrir un autre document.
En résumé, l'allocateur doit se charger des taches suivantes:
La réservation de pages mémoire est normalement transparent vis à vis de l'utilisateur. Par contre, la réservation et la libération de la mémoire sont des opérations en interface avec le programmeur. Les points d'entrée de ces deux opérations sont les fonctions malloc() et free(). A ces 2 opérations viennent s'ajouter realloc(), qui permet de modifier la taille d'uen zone allouée.
Voyons tout d'abord un allocateur minimaliste qui ne libère pas réellement la mémoire, mais dont nous pourrons nous servir le cas échéant pour débugger la mémoire.
L'allocateur minimaliste ne répond pas au problème de la libération. Il a de plus l'inconvéniant d'appeler des routines systèmes (sbrk) peu performantes à chaque malloc.
Un des points les plus importants lorsque l'on parle de langage évolué est la notion de typage. Nous ne parlerons pas de la notion théorique de typage, mais de la notion pratique.
Dans la pratique, un ordinateur ne reconnait aucun type de donnée quant à son stockage en mémoire. Il n'existe pas une mémoire spécifique pour les entiers et une autre pour les caractères. La seule exception à cette règle se trouve dans les registres des microprocesseurs qui, eux, pour des raisons de performances, différencient les nombres entiers des nombres réels.
La notion de type « de bas niveau » correspond à la taille mémoire sur laquelle est encodée une entité. Le type définit donc la taille de la donnée à manipuler.
Les types simples sont les types prédéfinis par le langage C: char, short, int, long, float, double, et les pointeurs (void *). Il est à noter que quel que soit le «type» de pointeur (char *, int * double *, void *), la taille d'encodage est toujours la même. Aussi, un pointeur générique peut être appelé void *.
Ces types ont des tailles fixées pour une architecture donnée, mais varient d'une architecture à l'autre. Il semble même que la taille d'un « octet » n'ai pas toujours été de 8bits2
Il est impératif de ne pas supposer de leur taille si l'on recherche la portabilité. On pourra utiliser sizeof(type) en lieu et place de la taille supposée.
1 #include <stdio.h> 2 int main(void) { 3 printf("sizeof(char) = %d\n", sizeof(char)); 4 printf("sizeof(short) = %d\n", sizeof(short)); 5 printf("sizeof(int) = %d\n", sizeof(int)); 6 printf("sizeof(long) = %d\n", sizeof(long)); 7 printf("sizeof(long long) = %d\n", sizeof(long long));3 8 printf("sizeof(float) = %d\n", sizeof(float)); 9 printf("sizeof(double) = %d\n", sizeof(double)); 10 printf("sizeof(void *) = %d\n", sizeof(void *)); 11 printf("sizeof(char *) = %d\n", sizeof(char *)); 12 printf("sizeof(int *) = %d\n", sizeof(int *)); 13 printf("sizeof(double *) = %d\n", sizeof(double *)); 14 return 0; 15 }
donne sur linux iX86:
sizeof(char) = 1 sizeof(short) = 2 sizeof(int) = 4 sizeof(long) = 4 sizeof(long long) = 84 sizeof(float) = 4 sizeof(double) = 8 sizeof(void *) = 4 sizeof(char *) = 4 sizeof(int *) = 4 sizeof(double *) = 4
A retenir:
|
Comme nous l'avons vu dans la description de la mémoire virtuelle, la mémoire est comparable à un tableau.
On ne peut, en théorie, accéder directement qu'a une case entière dont la taille correspond à la taille de la mémoire (32bits). Il n'est donc pas possible d'accéder directement à une fraction d'une case mémoire.
Or comme nous venons de le voir, les types de donnés n'ont pas obligatoirement des tailles identiques à la taille de la mémoire.
Prennons par exemple le caractère char. Celui-ci est codé sur un seul octet. Mais, dans le cas d'une mémoire de 32bits, la taille d'une case est de 4 octets. On devra donc laisser, théoriquement, 3 octets vides.
Dans la pratique, les choses sont un peu plus complexes car même si la taille du bus mémoire est prépondérante, tous les octets sont directement numérotés. Il est donc possible, par l'entremise d'outils inclus dans les microprocesseurs et les compilateurs, d'accéder à des données de 1, 2 ou 4 octets directement.
Certaines architectures imposent cependant certaines contraintes d'alignement: les données ne peuvent être stockées qu'à des adresses multiples de la taille sur laquelle est codée ces données et au maximum, à des adresses multiples de la taille du bus.
Pour vérifier si l'architecture possède une contrainte d'alignement, il suffit de compiler et de lancer le programme suivant:
1 int main(void) { 2 struct s { char a, b, c, d, e, f, g, h, i, j;} t; 3 int *pi; 4 pi = (int *) &(t.a); 5 printf("pi = %p, i = %d\n", pi, *pi); 6 pi = (int *) &(t.b); 7 printf("pi = %p, i = %d\n", pi, *pi); 8 pi = (int *) &(t.c); 9 printf("pi = %p, i = %d\n", pi, *pi); 10 pi = (int *) &(t.d); 11 printf("pi = %p, i = %d\n", pi, *pi); 12 pi = (int *) &(t.e); 13 printf("pi = %p, f = %d\n", pi, *pi); 14 pi = (int *) &(t.f); 15 printf("pi = %p, i = %d\n", pi, *pi); 16 pi = (int *) &(t.g); 17 printf("pi = %p, i = %d\n", pi, *pi); 18 return 0; 19 }
Si le programme se termine normalement, alors l'architecture n'impose pas de contrainte. Dans le cas contraire, le programme s'arrêtera brutalement avec un signal de type bus error et génèrera un core.
Pour linux AMD Athlon, le programme tourne normalement. Sur Solaris/UltraSparc, le programme s'arrète avec un "Bus error" ligne 5.
A retenir:
|
Les types composés peuvent être définis comme des agrégats de types simples ou/et composés.
La définition de types composés se fait à l'aide du mot réservé «struct», suivi de la description des différent composants.
Ces types composés définis par « struct » fonctionnent comme les types simples en ce qui concerne l'assignation5:
1 #include <stdio.h> 2 int main(void) { 3 struct s1 {int a, b;} A, B; 4 A.a = 1, A.b = 2, B.a = 3, B.b = 4; 5 A = B; 6 printf("A.a = %d\nA.b = %d\n", A.a, A.b); 7 return 0; 8 }
donne:
A.a = 3 A.b = 4
Par contre, il est impossible de faire des comparaisons sur ces types complexes (comme par exemple A==B).
La séquence des composants tient un rôle important dans la définition des types composés. En effet en raison des contraintes d'alignement précitées, les trois structures suivantes n'utiliseront pas la même quantité de mémoire:
1 #include <stdio.h> 2 struct fin { 3 char a; 4 char b; 5 char c; 6 char d; 7 float x; 8 float y; 9 float z; 10 }; 11 struct moyen { 12 char a; 13 char b; 14 float x; 15 char c; 16 char d; 17 float y; 18 float z; 19 }; 20 struct large { 21 char a; 22 float x; 23 char b; 24 float y; 25 char c; 26 float z; 27 char d; 28 }; 29 int main(void) { 30 printf("sizeof(char) = %d\n", sizeof(char)); 31 printf("sizeof(float) = %d\n", sizeof(float)); 32 printf("4 * sizeof(char) + 3 * sizeof(float) = %d\n", 4 * sizeof(char) + 3 * sizeof(float)); 33 printf("sizeof(fin) = %d\n", sizeof(struct fin)); 34 printf("sizeof(moyen) = %d\n", sizeof(struct moyen)); 35 printf("sizeof(large = %d\n", sizeof(struct large)); 36 return 0; 37 }
donne les valeurs suivantes (linux iX86/AMD):
sizeof(char) = 1 sizeof(float) = 4 4 * sizeof(char) + 3 * sizeof(float) = 16 sizeof(fin) = 16 sizeof(moyen) = 20 sizeof(large) = 28
Dans le cas de la structure « fin », la séquence optimise la trace mémoire de la structure. Les 4 caractères sont contigus dans 1 case memoire de 32 bits, suivis de 3 float dont la taille est ici de 4 octets chacuns.
Dans le cas de la structure « large », la trace mémoire est maximale: pour chaque caractère, 4 octets sont occupés pour 1 seul de réellement utilisé.
Dans le cas de la structure « moyen », la trace mémoire n'est pas complètement optimisée et les 2 couple de « char » sont codés sur chacun 4 octets.
A retenir:
|
Les tableaux sont des zones de mémoires réservées et censées recevoir un nombre maximum préétabli de données d'un type prédéfini. Cependant, un tableau ne possède pas d'indication a priori de sa propre taille.
Par exemple :
char a[4];
va réserver statiquement une zone de mémoire de la pile de 4 fois la
taille d'un « char ».
Cette zone est valide jusqu'à la fermeture du bloc en cours.
Elle est aussi adressable depuis des fonctions appelées par le bloc en cours:
1 #include <stdio.h> 2 #include <string.h> 3 void func2(char *string) { 4 strcpy(string, "func2"); 5 } 6 char *func1() { 7 char string[10]; 8 strcpy(string, "func1"); 9 printf("func1 (a): %s\n", string); 10 func2(string); 11 printf("func1 (b): %s\n", string); 12 return string; 13 } 14 int main(void) { 15 char *s; 16 s = func1(); 17 printf("main: %s\n", s); 18 return 0; 19 }
produira quelque chose qui doit ressembler à ça:
func1 (a): func1 func1 (b): func2 main: àóÿ¿éôÿàóÿôÿ¿àóÿ¿Hôÿ¿@
Mais il se peut aussi que le programme se termine par un core dump durant l'appel au dernier printf ligne 17 sur certaines plateformes si s est NULL.
En fait, la fonction "func1()" retourne un pointeur alloué dans la frame 2 (frame correspondant à "func1()"). Or, dès que l'on sort de la fonction func1() pour revenir dans main, la frame 2 est dépilée (changement de cartographie de la mémoire virtuelle). Les valeurs qui se trouvaient dans les variables allouées sur pile sont donc remplacée de manière arbitraire.
Contraitement aux types composés, il n'est pas possible d'assigner un tableau à un autre tableau. En effet, le "type tableau" ne connait pas sa taille, il ne peut pas être manipulé comme une structure.
Le type tableau est incomplet. Il n'est en fait qu'une utilisation déguisée du concept de pointeur.
A retenir:
|
Un pointeur est une zone de mémoire qui contient ou est suceptible de contenir une adresse mémoire.
Bien qu'il existe des sous types différents de pointeurs (void *, char *, short *, int *, long *, float *, double *, pointeurs de pointeurs, pointeurs sur fonctions), tous ces sous types de pointeurs ont la même taille et sont intercheangeables.
Il exite deux opérations de base : & et *
& pourrait encore s'écrie « adresse de »
et * pourrait s'écrire « valeur de »
Il est d'ailleurs tout à fait possible d'écrire des macros:
#define addressof(x) &(x) #define valueof(x) *(x)
Exemple:
int i = 1; int *pi = NULL; pi = &i; /* pi = addressof(i) */ *pi = 2; /* valueof(pi) = 2 */
La notion de tableau est intrinsèquement liée à la notion de pointeur. En effet, si l'on considère la mémoire comme un tableau, le pointeur devient une manière d'adresser ce tableau de manière absolue, alors que le tableau au sens C est adressé de manière relative à son début par un indexe.
Un nom de tableau est
équivalent à un pointeur sur le premièr élément
du tableau:
a[0]f est équivalent à *a.
On peut aussi référencer le ième élément d'un
tableau a de deux manière:
a[i] ou *(a + i).
La définition de
tableaux multidimentionnelle passe aussi de manière implicite
par l'utilisation des pointeurs:
Un tableau bi-dimensionnel est un tableau de pointeurs sur tableaux unidimensionnels:
definition statique sur la pile:
float a[10][5];
définition dynamique et allocation:
float **b; /* allocation d'un tableau de 10 pointeurs sur (float *) */ *b = (float **) malloc(10 * sizeof(float)); for (i = 0; i < 10; i++) /* allocation de 5 float */ b[i] = (float *) malloc(5 * sizeof(float));
Les structures peuvent elles aussi être désignées par des pointeurs. Il est même possible d'accéder directement à un champs d'une structure à partir d'un pointeur sur structure. L'opérateur de membre passe alors de « . » à « -> »:
struct complex {float a; float b} c, *pc; c.a = 1., c.b = 0., pc = &c; pc->a = 2., pc->b = 1.;
Mais ce ne sont pas les seules façons de référencer des membres de structures. En effet, il existe une macro offsetof() (définie dans stddef.h) permettant de retrouver l'offset (décallage) d'un champ par rapport au premier élément de la structure qui le contient. A partir de ce décallage et en opérant les recasting qui s'imposent, il est possible d'accéder à tous les champs d'une structure :
#include <stddef.h> #include <stdio.h> #include <string.h> int main(void) { typedef struct { int a; char b[10]; double c; } S; int a_shift, b_shift, c_shift; S s; char *p; p = (char *) &s; a_shift = offsetof(S, a); b_shift = offsetof(S, b); c_shift = offsetof(S, c); printf("offset a = %d\noffset b = %d\noffset c = %d\n", a_shift, b_shift, c_shift); *((int *) (p + a_shift)) = 555; strcpy(p + b_shift, "abcdef"); *((double *) (p + c_shift)) = 3.14159; printf("s.a = %d\ns.b = \"%s\"\ns.c = %lf\n", s.a, s.b, s.c); printf("*(int *) (p+a_shift) = %d\n (p+b_shift) = \"%s\"\n*(double *) (p+c_shift) = %lf\n", *(int *) (p+a_shift), (p+b_shift), *(double *) (p+c_shift)); return 0; }
donne :
offset a = 0 offset b = 4 offset c = 16 s.a = 555 s.b = "abcdef" s.c = 3.141590 *(int *) (p+a_shift) = 555 (p+b_shift) = "abcdef" *(double *) (p+c_shift) = 3.141590
Pour plus d'exemples, reportez-vous au chapitre "Exemple récapitulatif"".
Il est possible de définir des variable « pointant » sur des fonctions.
Ces pointeurs sur fonctions sont à la base du principe de « virualisation » et, de manière plus générale, de la réutilisation de fonctions quel que soit le type des données fournies. Cette réutilisation à partir d'un typage faible, voire une absence de typage, constitue une implémentation plus élégante et plus concise que l'utisation des templates du C++.
Il est nécessaire, lors de l'usage de pointeurs sur fonctions, de définir un protocole spécifique de passage de paramètres (nombre de ces paramètres).
Le premier exemple qui vient à l'esprit lorsqu'on parle de pointeur sur fonction est la fonction qsort (dont le prototype est dè dans stdlib.h):
void qsort(void *base, size_t nmemb, size_t size, int(*compar)(const void *, const void *));
Le variable de type pointeur sur fonction s'appelle compar.
Utilisons la fonction qsort:
#include <stdlib.h> #include <stdio.h> int float_comp(float *a, float *b) { if (*a > *b) return 1; if (*a < *b) return -1; return 0; } void arr_print(float *arr) { int i; for (i = 0; i < 10; i++) printf("arr[%d] = %3.1f\n", i, arr[i]); } int main(void) { float arr[10] = {5.1, 4.2, 3.3, 1.4, 7.8, 2.0, 8.9, 9.7, 0.5, 6.6}; printf("Tableau initial:\n"); arr_print(arr); qsort(arr, 10, sizeof(float), float_comp); printf("\nTableau final:\n"); arr_print(arr); return 0; }
donne:
Tableau initial: arr[0] = 5.1 arr[1] = 4.2 arr[2] = 3.3 arr[3] = 1.4 arr[4] = 7.8 arr[5] = 2.0 arr[6] = 8.9 arr[7] = 9.7 arr[8] = 0.5 arr[9] = 6.6 Tableau final: arr[0] = 0.5 arr[1] = 1.4 arr[2] = 2.0 arr[3] = 3.3 arr[4] = 4.2 arr[5] = 5.1 arr[6] = 6.6 arr[7] = 7.8 arr[8] = 8.9 arr[9] = 9.7
Dans cet exemple, la fonction qsort utilise un pointeur sur une fonction de pomparaison. Nous avons vu comment définir la foncition de comparaison (ici float_comp) et comment la passer en paramètre à la fonction qsort.
La compilation du code précédent produit néanmoins un message d'avertissement à cause de
la différence de typage entre la fonction float_comp() et la fonction attendue par qsort.
int float_comp(void *a, void *b) { if (*(float *)a > *(float *)b) return 1; ...
qsort(arr, 10, sizeof(float), (int(*)(const void *, const void *)) float_comp);
Il est également possible de simplifier l'écriture de la seconde méthode en définissant un type de pointeur sur fonction:
typedef int (*comp_f)(const float *, const float *);
Le recast de la fonction float_comp s'écrira alors:
qsort(arr, 10, sizeof(float), (comp_t) float_comp);
Les pointeurs sur fonctions sont aussi très utils pour la définition de "classes". Les meilleurs exemples de ces techniques se trouvent dans l'implémentations des systèmes de fichiers du noyau Linux, ou encore dans l'architecture GTK/Gnome.
En ce qui concerne le noyau Linux, on pourra regarder le header linux/fs.h, et en particulier la structure address_space_operations (noyau 2.4.18):
struct address_space_operations { int (*writepage)(struct page *); int (*readpage)(struct file *, struct page *); int (*sync_page)(struct page *); /* * ext3 requires that a successful prepare_write() call be followed * by a commit_write() call - they must be balanced */ int (*prepare_write)(struct file *, struct page *, unsigned, unsigned); int (*commit_write)(struct file *, struct page *, unsigned, unsigned); /* Unfortunately this kludge is needed for FIBMAP. Don't use it */ int (*bmap)(struct address_space *, long); int (*flushpage) (struct page *, unsigned long); int (*releasepage) (struct page *, int); #define KERNEL_HAS_O_DIRECT /* this is for modules out of the kernel */ int (*direct_IO)(int, struct inode *, struct kiobuf *, unsigned long, int); };
Cette structure définit les méthodes communes à tous les systèmes de fichiers.
Pour plus d'exemples, reportez-vous au chapitre "Exemple récapitulatif"".
La désignation de données peut s'effectuer de plusieurs manières par valeur, par adresse
La désignation d'une donnée par sa valeur se fait soit en appelant le nom de la variable, si celle-ci n'est pas un pointeur vers la donnée:
int i = 1;
i est alors « mis pour » 1.
Si la variable est un
pointeur, il est nécessaire de passer par l'opératieur
« valueof() » (*):
int i = 1, *pi; pi = &i; /* pi = addressof(i) */
Alors, *pi est « mis pour » 1;
La désignation par adresse se fait soit en prenant l'adresse d'une variable par l'opérateur « addressof() » (&):
float f = 2.;
&f désigne l'adresse de la variable f.
Ou encore, si la variable est un pointeur:
float f = 2., *pf; pf = &f;
pf désigne l'adresse de f
L'appel à une fonction recopie les valeurs des paramètres sur la pile. On a donc une copie locale des données accessibles à partir de nouveaux noms de variables, ceux définis dans le prototype d'appel à la fonction. Si une modification de la valeur d'une telle variable est faite, cette modification est locale.
Si une variable passée en paramètre est un pointeur et que l'on modifie la valeur pointée:
*p = valeur; /*valueof(p) = valeur */
alors, la modification n'est plus locale:
soit le programme:
1 #include <stdio.h> 2 void func(int i, int j, int *pk) { 3 printf("&i = %p (%u), &j = %p (%u), &pk = %p (%u)\n", &i, &i, &j, &j, &pk, &pk); 4 *pk = i + j; 5 i = 3, j = 4; 6 printf("i = %d, j = %d, pk = %x (%u), *pk = %d\n", i, j, pk, pk, *pk); 7 } 8 int main(void) { 9 int a = 1, b = 2, c = 0; 10 printf("&a = %p (%u), &b = %p (%u), &c = %p (%u)\n", &a, &a, &b, &b, &c, &c); 11 printf("a = %d, b = %d, c = %d\n", a, b, c); 12 func(a, b, &c); 13 printf("a = %d, b = %d, c = %d\n", a, b, c); 14 return 0; 15 }
&a = 0xbffff4b4 (3221222580), &b = 0xbffff4b0 (3221222576), &c = 0xbffff4ac (3221222572) a = 1, b = 2, c = 0 &i = 0xbffff490 (3221222544), &j = 0xbffff494 (3221222548), &pk = 0xbffff498 (3221222552) i = 3, j = 4, pk = bffff4ac (3221222572), *pk = 3 a = 1, b = 2, c = 3
Les variables i et j sont bien locales à fonction func. La modification de leur valeur n'est que locale.
Par contre, le passage d'un pointeur en argument permet la modification de la valeur pointée.
Voici un petit exemple récapitulatif. A vous de retrouver les différentes techniques vues et
d'ajouter les commentaires.
(ce code compile sans erreur ni warning avec gcc -Wall -ansi -pedantic)
#include <stdlib.h> #include <stdio.h> void * object_new(); void * object_destroy(void *); typedef struct { int type; void * (*destroy)(void *); void * (*new)(); } object_t, *pobject_t; object_t object_defaults() { object_t o; o.type = 1; o.new = object_new; o.destroy = object_destroy; return o; } typedef struct { object_t o; int (*get)(void *); int (*set)(void *, int); int i; } int_t, *pint_t; void *object_destroy(void *po) { printf("destroy object_t\n"); if (!po) return NULL; free(po); return NULL; } void * object_new() { pobject_t po; printf("create object_t\n"); if (!(po = (pobject_t) malloc(sizeof(object_t)))) return NULL; *po = object_defaults(); return (void *) po; } int int_get(void *pi) { if (!pi) return -1; return ((pint_t) pi)->i; } int int_set(void *pi, int i) { if (!pi) return -1; return ((pint_t) pi)->i = i; } void * int_new() { pint_t pi; printf("create int_t\n"); if (!(pi = (pint_t) malloc(sizeof(int_t)))) return NULL; pi->o = object_defaults(); pi->o.type = 2; pi->o.new = int_new; pi->i = 0; pi->get = int_get; pi->set = int_set; return pi; } int main(void) { pobject_t po; pint_t pi; po = (pobject_t) (pi = int_new()); printf("po = %p\npi = %p\n", (void *) po, (void *) pi); printf("type de po: %d\n", po->type); printf("type de pi: %d\n", pi->o.type); pi->set((void *) pi, 555); printf("valeur de pi->i = %d\n", pi->i); printf("valeur de pi->get(pi) = %d\n", pi->get((void *) pi)); printf("valeur de ((pint_t) po)->i = %d\n", ((pint_t) po)->i); printf("valeur de ((pint_t) po)->get((void *) po) = %d\n", ((pint_t) po)->get((void *) po)); ((pobject_t) pi)->destroy((void *) pi); return 0; }
donne:
create int_t po = 0x8049aa0 pi = 0x8049aa0 type de po: 2 type de pi: 2 valeur de pi->i = 555 valeur de pi->get(pi) = 555 valeur de ((pint_t) po)->i = 555 valeur de ((pint_t) po)->get((void *) po) = 555 destroy object_t
Lorsque l'on a affaire à des constantes, ou des variables bien définies et fixées en terme de taille, et a usage local seulement, l'allocation sur la pile est appropriée.
Lorsque les variables ont des tailles non fixées ou encore ne sont pas à usage local, il est sûr d'utiliser l'allocation dynamique.
#include <stdlib.h> void *malloc(size_t size);
Malloc prend en argument la taille de la zone mémoire désirée et retourne le pointeur sur la zone allouée ou 0 (pointeur nul) s'il n'y a pas assez d'espace mémoire contigu.
Une zone mémoire fraichement allouée par malloc n'est a priori pas initialisée et peut contenir n'importe quoi (données aléatoires provenant d'anciennes applications ayant utilisé le même espace, ou données aléatoires).
Il est donc nécessaire de l'initialiser avec des valeurs appropriées (par exemple 0) en fonction du type de donées à inserer dans ces zones allouées et / ou en fonction des règles de programmation utilisées. On pourra initialiser l'ensemble de la zone avec la fonction memset(), recopier des zones existantes avec memcpy(), ou encore initialiser les blocs en fonction de leur découpage (types, structures, tableaux, ...).
A retenir:
|
#include <stdlib.h> void *calloc(size_t nmemb, size_t size);
A la différence de malloc(), calloc() alloue un tableau de nmemb élements ayant chacun size pour taille. La mémoire allouée est initialisée à 0.
Pour le reste, calloc() fonctionne comme malloc(): si la mémoire a bien été réservée et initialisée, calloc() retourne le pointeur sur la zone. Sinon, calloc() returne 0 (pointeur nul).
#include <stdlib.h> void free(void *ptr);
La fonction free() annule la réservation d'une zone de mémoire pointée par ptr, allouée par malloc() ou calloc() ou par toute autre fonction réservant de la mémoire, comme strdup() qui duplique une chaine de caractères.
Si l'on tente de libérer un espace mémoire qui n'a pas été allouée par malloc() ou calloc(), directement ou par le biais d'une fonction appelant malloc() ou calloc(), la fonction free() enverra un signal 11 (SIGSEGV « Invalid memory reference ») qui terminera brutalement le programme.
La fonction free() n'écrase pas la mémoire de la zone.
Il est fortement recommandé d'annuler le pointeur qui vient d'être libéré par free(), afin de ne pas tenter d'adresser un espace qui pourra être réutilisé. On pourra par exemple réécrire une fonction:
void *my_free(void *ptr) { if (ptr) free(ptr); return NULL; }
et l'utiliser de la manière suivante:
ptr = my_free(ptr);
ou encore écrire la macro qui s'utilise de la même manière que free():
#define FREE(x) { if (x) free(x); x = NULL; }
#include <stdlib.h> void *realloc(void *ptr, size_t size);
La fonction realloc() prend en argument le pointeur sur la zone de mémoire (ptr) dont il faut modifier la taille, et la nouvelle taille désirée (size). Si l'opération s'est bien passée, realloc() returne le pointeur sur la zone mémoire.
Attention:
il est fort possible que le nouveau pointeur soit
différent de l'ancien! En effet, si la zone mémoire
doit être augmentée, et que la zone en cours n'est pas
assez grande pour supporter la nouvelle taille, realloc() allouera
une nouvelle zone de mémoire contigue et recopiera
complètement l'ancienne zone.
Il est donc impératif de ne pas adresser directement par un pointeur un espace mémoire dont la taille doit/peut être modifiée par realloc:
Les zones en rouge sont
des zones de mémoire occupées. La zone jaune est la
zone de mémoire que nous avons réservé et
modifié par realloc. Si nous conservons un pointeur sur la
zone avant le réalloc, ce pointeur ne sera plus utilisable
après car in adressera une zone non réservée et
contenant des données aléatoires.
Si l'opération échoue, realloc() retourne 0, mais ne déasalloue pas la zone d'origine. Il est impératif de ne pas réassigner le même pointeur immédiatement avec le résultat de realloc() au risque de perdre toutes les données en mémoire et de ne jamais pouvoir les libérer.
Ce qu'il ne faut pas faire:
ptr = realloc(ptr, newsize);
Ce qu'il faut faire:
{ void *p; if ((p = realloc(ptr, newsize))) { /* la réallocation s'est bien passée , l'affectation est sûre: */ ptr = p; } else { /* la réallocation a échoué, le processus ne peut pas continuer mais la mémoire est encore utilisée: */ free (ptr); } return NULL; }
#include <string.h> void *memset(void *s, int c, size_t n);
memset() prend en argument le pointeur sur la zone allouée (s) à initialiser, la valeur à affecter à toute la zone mémoire (c), et le nombre d'octets devant subir le traitement (n).
Il est à noter que seul le premier octet de la valeur d'initialisation est pris en compte.
memset retourne le pointeur d'entrée (s).
#include <string.h> void *memcpy(void *dest, const void *src, size_t n);
memcpy prend en entrée le pointeur zone de mémoire allouée de destination, le poinrteur sur la zone de mémoire cible, et le nombre d'octets devant être copiés.
memcpy retourne le pointeur de destination (dest).
Attention:
#include <string.h> void *memmove(void *dest, const void *src, size_t n);
La fonction memmove() déplace le contenu d'une zone mémoire vers une autre. Elle prend en argument le pointeur vers la zone mémoire allouée vers laquelle déplacer le contenu mémoire (dest), le pointeur vers la zone de départ de la copie (src) et le nombre d'octets à déplacer. Elle retourne le pointeur vers la zone de destination (dest).
Contrairement à memcpy(), il est possible que les 2 zones se supperposent.
La manipulation de chaines de caractères est une source de nombreux problèmes dont sont victime autant les programmeurs débutants que les chevronnés. Ces quelques lignes permettrons peut être à certains d'éviter quelques écueils.
Une chaine de caractères est un tableau d'octets terminé par un octet nul (ou caractère nul = 0 = '\0').
Si l'on veut réserver la mémoire pour la chaine « bonjour, monde », il est nécessaire de penser que cette chaine doit en fait s'écrire « bonjour, monde\0 » et réserver l'espace en conséquence.
La fonction strlen() retourne la taille d'une chaine de caractère sans son terminateur.
L'indexe d'un tableau commence toujours par 0 et non par 1. Aussi, lorsque l'on réserve 50 chars pour une chaine, le caractère nul devra se trouver au plus à l'index 49.
Certaines fonctions nécessitent une attention toute particulière:
#include <string.h> char *strcpy(char *dest, const char *src);
La fonction strcpy recopie le contenu de la chaine src vers la chaine allouée pointée par dest et retourne le pointeur vers la chaine de destination (le caractère nul est inclus dans la copie).
Mais attention: il est
nécessaire au préalable de s'être assuré
que la taille réservé dans dest est supérieure
ou égale à la taille de la chaine pointée par
src.
Si ce n'est pas le cas, nous risquons d'avoir un écrasement
d'une zone mémoire.
Cet écrasement peut modifier des
données stockées sur la pile, ou pire, écraser
une partie du programme lui même puisque la définition
de la pile précède le code dans la mémoire.
Ce défaut est la base de la technique de piratage la plus
répendue: le pirate fournit au programme une chaine assez
longue pour empiéter sur le code de la fonction et contient un
autre code au format binaire qui sera exécuté au lieu
du code de la fonction (voir Linux Mag # ...)
#include <string.h> char *strncpy(char *dest, const char *src, size_t n);
strncpy() prend les mêmes arguments que strcpy() plus n, le nombre maximal d'octets à recopier.
Mais, là encore, attention car strncpy() recopie au plus n caractères sans pour autant tester que le dernier caractère copié est bien NULL. Il est donc nécessaire de s'assurer après un strncpy() que le dernier caractère est bien nul:
char* mystrncpy(char *dest, char *src, size_t n) { if (!dest) return 0; if (!src || n <= 1) return *dest = 0; strncpy(dest, src, n); dest[n - 1] = 0; return dest; }
#include <string.h> char *strcat(char *dest, const char *src);
strcat ajoute à la chaine dest la chaine src. Il est nécessaire d'être sûr d'avoir réservé assez de place dans la chaine dest pour recevoir la chaine src en plus de son contenu. De même que pour strncpy, il existe une variation permettant de limiter la longueur de la chaine globale:
char *strncat(char *dest, const char *src, size_t n)
strncat() ne recopie que les n premiers octets de src à la fin de dest et ajoute 0 à la fin, 0 n'étant pas pris en compte dans n.
Pour ne pas faire de débordement mémoire, il faut donc que n soit égal à la taille réservée de dest, diminué de strlen(dest) et encore diminué de 1 pour le caractère nul final.
#include <stdio.h> int sprintf(char *str, const char *format, ...);
Cette fonction « imprime » remplit une chaine de caractères allouée (str) à partir d'un format (format) et, eventuellement, des données (...) et retourne le nombre de caractères de la nouvelle chaine (caractère nul final non compris). Une fois encore, aucun test n'est effectué sur la taille maximale de la chaine de caractères de destination. Pour cette fonction aussi, il existe une « parade »: la fonction snprintf().
int snprintf(char *str, size_t size, const char *format, ...);
snprintf() tronque la nouvelle chaine « str » à size caractères y compris le caractère nul final. Dans le cas où la chaine str est inférieure à size, la fonction retourne la taille de str. Sinon, la taille de str est size - 1 et la fonction retourne -1.
Il existe d'autres groupes de fonctions avec des propriétés similaires, telles que strcmp et strncmp, strdup et strndup, vsprintf et vsnprintf (les équivalent de sprintf et snprintf mais dont le le dernier argument est une va_list (voir man vsprintf et man stdarg).
L'erreur la plus fréquente est la tentative de réréférencement du pointeur nul.
Elle se produit dans plusieurs cas :
Ces cas ne sont ni exhaustifs, ni mutuellement exclusifs.
Un pointeur null pointe vers l'adresse 0x0. Celle-ci est protégée et ne peut pas être utilisée, ni en lecture, ni en écriture.Si un pointeur pointe vers 0x0 et que le programme tente de déréférencer le pointeur (i.e. d'aller voir à l'adresse 0x0), il s'ensuit systematiquement un erreur de segmentation mémoire :
% cat > defre0.c int main() { char *p = 0; p[0] = 1; } ^D % cc deref0.c -o deref0 % deref0 zsh: segmentation fault (core dumped) deref0 % echo $? 139 %
Pour utiliser un pointeur, il est nécessaire qu'il pointe sur une adresse correcte.
Pour éviter ce genre d'erreur, il est nécessaire de toujours allouer un pointeur :
char *p; p = (char *) malloc(12);
ou de lui assigner une adresse valide :
char buf[512]; char *p; p = buff;
Il est aussi nécessaire de tester la cohérence (au moins partielle) d'un pointeur lorsque l'on ne sait pas quelle valeur il peut avoir :
void myfunc(char *str) { char *p; p = (char *) malloc(12); /* check p was correctly initialized : */ if (p) { ... } else { fprintf(stderr, "p was not allocated (p = %x)\n", p); perror("reason: "); } if (str) { ... } else { fprintf(stderr, "str is null and therefore cannot be used!!!\n") }
Il est important de noter qu'un pointeur non nul n'implique pas pour autant sa validité.
Une autre erreur très fréquente est le passage d'un argument par sa valeur. Une fonction est supposée changer la valeur d'une variable. La variable est modifiée dans la fonction, mais se retrouve inchangée dans juste après le retour :
#include <stdio.h> void set(int i) { printf("set : i = %d\n", i); i = 3; printf("set : i = %d\n", i); } int main(void) { int i; i = 2; printf("main: i = %d\n", i); set(i); printf("main: i = %d\n", i); return 0; }
donne :
main: i = 2 set : i = 2 set : i = 3 main: i = 2
Le passage par valeur recopie la valeur. Il ne faut donc pas confondre la variable i à l'intérieur de la fonction set avec le i à l'intérieur de la fonction main! D'ailleurs pour s'en convaincre :
#include <stdio.h> void set(int i) { printf("set : &i = %x\n", &i); printf("set : i = %d\n", i); i = 3; printf("set : i = %d\n", i); } int main(void) { int i; i = 2; printf("main: &i = %x\n", &i); printf("main: i = %d\n", i); set(i); printf("main: i = %d\n", i); return 0; }
donne :
main: &i = ffbef4d8 main: i = 2 set : &i = ffbef4bc set : i = 2 set : i = 3 main: i = 2
Si l'on veut modifer une variable dans une sous-fonction, il est nécessaire de passer le pointeur sur la variable, plutôt que la variable elle même.
#include <stdio.h> void set(int *i) { if (!i) { printf("invalid pointer\n"); return; } printf("set : i = %x\n", i); printf("set : *i = %d\n", *i); *i = 3; printf("set : *i = %d\n", *i); } int main(void) { int i; i = 2; printf("main: &i = %x\n", &i); printf("main: i = %d\n", i); set(&i); printf("main: i = %d\n", i); return 0; }
donne :
main: &i = ffbef4d8 main: i = 2 set : i = ffbef4d8 set : *i = 2 set : *i = 3 main: i = 3
L'utilisation d'une variable non initialisée peut avoir des conséquences facheuses. En effet, nous prenons souvent pour postulat que toute variable non initialisée est mise à 0. Dans certains cas, le postulat fonctionne. Mais ce n'ai qu'un fait du hasard car la remise à zero n'est pas automatique. Il est possible que la zone fraichement allouée ait été auparavent déjà utilisée :
pour s'en convaincre :
#include#include int main(void) { char *a = NULL; char *b = NULL; if (!(a = (char *) malloc(12))) { fprintf(stderr, "cannot reserve memory for a\n"); return 1; } strcpy(a, "0123456789A"); printf("a = \"%s\"\n", a); free(a); if (!(b = (char *) malloc(12))) { fprintf(stderr, "cannot reserve memory for b\n"); return 2; } printf("b = \"%s\"\n", b); return 0; }
donne
a = "0123456789A" b = "0123456789A"
pour remédier au problème, on pourra, dans le cas d'une chaine de caractères, initialiser b de la manière suivante:
*b = 0;
ou
b[0] = '\0';
Qui sont 2 expression rigoureusement équivalentes.
En revanche
memset(b, 0, 12);
n'initialisera pas seulement le 1er octet de la chaine à 0, mais les 12 octets réservés.
L'adressage sur la pile ne pose pas de problème quant on comprend comment il fonctionne. Mais il arrive de voir ce genre de choses :
#include <stdio.h> #include <string.h> char *init_string() { char buff[16]; strcpy(buff, "première chaine"); return buff; } void print_string(char *str) { printf("string is = \"%s\"\n", str); } int main(void) { char *str; str = init_string(); print_string(str); return 0; }
donne sous Solaris :
string is = "ÿ¾ôdine"
voyons ce qui ce passe :
dbx% dis main 0x000107f0: main : save %sp, -0x68, %sp 0x000107f4: main+0x0004: call init_string 0x000107f8: main+0x0008: nop 0x000107fc: main+0x000c: st %o0, [%fp - 0x8] 0x00010800: main+0x0010: ld [%fp - 0x8], %l0 0x00010804: main+0x0014: call print_string 0x00010808: main+0x0018: or %l0, %g0, %o0 0x0001080c: main+0x001c: ba main+0x2c 0x00010810: main+0x0020: st %g0, [%fp - 0x4] 0x00010814: main+0x0024: ba main+0x2c dbx% stop in main (2) stop in main dbx% run Running: addpile (process id 26360) stopped in main at line 13 in file "addpile.c" 13 str = init_string(); dbx% ex $sp/64 0xffbef3f8: 0xff33c5a8 0x00000000 0x00000000 0x00000000 0xffbef408: 0x00000000 0x00000000 0x00000000 0xff3e66b4 0xffbef418: 0x00000001 0xffbef4c4 0xffbef4cc 0x00020800 0xffbef428: 0x00000000 0x00000000 0xffbef460 0x00010738 0xffbef438: 0x00000000 0x00000000 0x00000003 0xffbef4c4 0xffbef448: 0x00000004 0xffbef4cc 0x00000005 0xffbef610 0xffbef458: 0x00000000 0x00000000 0x00000001 0xffbef4c4 str area 0xffbef468: 0xffbef4cc 0x00020800 0xffbef4cc 0x00000000 0xffbef478: 0x00000000 0x00000000 0x00000000 0x00000000 0xffbef488: 0x00000000 0x00000000 0x00000000 0x00000000 0xffbef498: 0x00000000 0x00000000 0x00000000 0x00000000 0xffbef4a8: 0x00000000 0x00000000 0x00000000 0x00000000 0xffbef4b8: 0x00000000 0x00000000 0x00000001 0xffbef688 0xffbef4c8: 0x00000000 0xffbef6aa 0xffbef6b7 0xffbef7af 0xffbef4d8: 0xffbef7ba 0xffbef7e6 0xffbef7f1 0xffbef7fc 0xffbef4e8: 0xffbef80d 0xffbef819 0xffbef825 0xffbef846 dbx% p &str &str = 0xffbef458 dbx% step stopped in init_string at line 5 in file "addpile.c" 5 strcpy(buff, "première chaine"); dbx% dbx% print &buff &buff = 0xffbef3e4 dbx% ex $sp/64 0xffbef380: 0x00000000 0x00000000 0x00000000 0x00000000 0xffbef390: 0x00000000 0x00000000 0x00000000 0x00000000 0xffbef3a0: 0x00000000 0xff340060 0xff33c590 0x00000300 0xffbef3b0: 0x000239b0 0xff29bb60 0xffbef3f8 0x000107f4 return address in main 0xffbef3c0: 0x00000000 0xffbef4c4 0xffbef4cc 0x00010034 0xffbef3d0: 0xff3b10f0 0x000006ee 0xff3e6da4 0xff3b17de 0xffbef3e0: 0x00000000 0x00000000 0x00000000 0x00000000 buff area 0xffbef3f0: 0x00000000 0x00000000 0xff33c5a8 0x00000000 0xffbef400: 0x00000000 0x00000000 0x00000000 0x00000000 0xffbef410: 0x00000000 0xff3e66b4 0x00000001 0xffbef4c4 0xffbef420: 0xffbef4cc 0x00020800 0x00000000 0x00000000 0xffbef430: 0xffbef460 0x00010738 0x00000000 0x00000000 0xffbef440: 0x00000003 0xffbef4c4 0x00000004 0xffbef4cc 0xffbef450: 0x00000005 0xffbef610 0x00000000 0x00000000 0xffbef460: 0x00000001 0xffbef4c4 0xffbef4cc 0x00020800 0xffbef470: 0xffbef4cc 0x00000000 0x00000000 0x00000000 dbx% n stopped in init_string at line 6 in file "addpile.c" 6 return buff; dbx% ex $sp/64 0xffbef380: 0xffbef3e4 0x0001086c 0x00000000 0x00000000 0xffbef390: 0x00000000 0x00000000 0x00000000 0x00000000 0xffbef3a0: 0x00000000 0xff340060 0xff33c590 0x00000300 0xffbef3b0: 0x000239b0 0xff29bb60 0xffbef3f8 0x000107f4 return address in main(); 0xffbef3c0: 0x00000000 0xffbef4c4 0xffbef4cc 0x00010034 0xffbef3d0: 0xff3b10f0 0x000006ee 0xff3e6da4 0xff3b17de 0xffbef3e0: 0x00000000 0x7072656d 0x69687265 0x20636861 buff area set 0xffbef3f0: 0x696e6500 0x00000000 0xff33c5a8 0x00000000 0xffbef400: 0x00000000 0x00000000 0x00000000 0x00000000 0xffbef410: 0x00000000 0xff3e66b4 0x00000001 0xffbef4c4 0xffbef420: 0xffbef4cc 0x00020800 0x00000000 0x00000000 0xffbef430: 0xffbef460 0x00010738 0x00000000 0x00000000 0xffbef440: 0x00000003 0xffbef4c4 0x00000004 0xffbef4cc 0xffbef450: 0x00000005 0xffbef610 0x00000000 0x00000000 0xffbef460: 0x00000001 0xffbef4c4 0xffbef4cc 0x00020800 0xffbef470: 0xffbef4cc 0x00000000 0x00000000 0x00000000 dbx% ex 0xffbef3e4 0xffbef3e4: 0x7072656d dbx% n stopped in init_string at line 7 in file "addpile.c" 7 } dbx% n stopped in main at line 14 in file "addpile.c" 14 print_string(str); dbx% print -f%x $sp $sp = ffbef3f8 stack pointer back to main's stack frame dbx% ex 0xffbef380/64 0xffbef380: 0xffbef3e4 0x0001086c 0x00000000 0x00000000 0xffbef390: 0x00000000 0x00000000 0x00000000 0x00000000 0xffbef3a0: 0xffbef3e4 0xff340060 0xff33c590 0x00000300 0xffbef3b0: 0x000239b0 0xff29bb60 0xffbef3f8 0x000107f4 saved frame pointer (pointer to main()'s stack frame) 0xffbef3c0: 0x00000000 0xffbef4c4 0xffbef4cc 0x00010034 0xffbef3d0: 0xff3b10f0 0x000006ee 0xff3e6da4 0xff3b17de 0xffbef3e0: 0x00000000 0x7072656d 0x69687265 0x20636861 0xffbef3f0: 0x696e6500 0xffbef3e4 0xff33c5a8 0x00000000 0xffbef400: 0x00000000 0x00000000 0x00000000 0x00000000 0xffbef410: 0x00000000 0xff3e66b4 0x00000001 0xffbef4c4 0xffbef420: 0xffbef4cc 0x00020800 0x00000000 0x00000000 0xffbef430: 0xffbef460 0x00010738 0x00000000 0x00000000 0xffbef440: 0x00000003 0xffbef4c4 0x00000004 0xffbef4cc 0xffbef450: 0x00000005 0xffbef610 0xffbef3e4 0x00000000 str points to &buff 0xffbef460: 0x00000001 0xffbef4c4 0xffbef4cc 0x00020800 0xffbef470: 0xffbef4cc 0x00000000 0x00000000 0x00000000 dbx% p str p str str = 0xffbef3e4 "première chaine" dbx% s stopped in print_string at line 9 in file "addpile.c" 9 printf("string is = \"%s\"\n", str); dbx% print -f%x $sp $sp = ffbef398 stack pointer for print_string() dbx% ex 0xffbef380/64 0xffbef380: 0xffbef3e4 0x0001086c 0x00000000 0x00000000 0xffbef390: 0x00000000 0x00000000 0x00000000 0x00000000 0xffbef3a0: 0x00000000 0x00000000 0x00000000 0x00000000 0xffbef3b0: 0x00000000 0x00000000 0xffbef3e4 0xff340060 0xffbef3c0: 0xff33c590 0x00000300 0x000239b0 0xff29bb60 0xffbef3d0: 0xffbef3f8 0x00010804 0xff3e6da4 0xff3b17de saved fp and return address in main() 0xffbef3e0: 0x00000000 0x7072656d 0x69687265 0x20636861 string is still there! 0xffbef3f0: 0x696e6500 0xffbef3e4 0xffbef3e4 0x00000000 0xffbef400: 0x00000000 0x00000000 0x00000000 0x00000000 0xffbef410: 0x00000000 0xff3e66b4 0x00000001 0xffbef4c4 0xffbef420: 0xffbef4cc 0x00020800 0x00000000 0x00000000 0xffbef430: 0xffbef460 0x00010738 0x00000000 0xffbef3e4 0xffbef440: 0x00000003 0xffbef4c4 0x00000004 0xffbef4cc 0xffbef450: 0x00000005 0xffbef610 0xffbef3e4 0x00000000 0xffbef460: 0x00000001 0xffbef4c4 0xffbef4cc 0x00020800 0xffbef470: 0xffbef4cc 0x00000000 0x00000000 0x00000000 dbx% n abracadabra... string is = "" stopped in print_string at line 10 in file "addpile.c" 10 } dbx% ex 0xffbef380/64 0xffbef380: 0x00000000 0x00000000 0x00000000 0x00000000 0xffbef390: 0xffbef398 0x000107c8 0x0001087c 0xffbef3e4 0xffbef3a0: 0x00000000 0x00000000 0x00000000 0x00000000 0xffbef3b0: 0x00000000 0x00000000 0xffbef3e4 0xff340060 0xffbef3c0: 0xff33c590 0x00000300 0x000239b0 0xff29bb60 0xffbef3d0: 0xffbef3f8 0x00010804 0xff3e6da4 0xff3b17de 0xffbef3e0: 0xffbef3e4 0x00000000 0x00000000 0x00000000 pfffft... no more string :( 0xffbef3f0: 0x00000000 0xffbef3e4 0xffbef3e4 0x00000000 0xffbef400: 0x00000000 0x00000000 0x00000000 0x00000000 0xffbef410: 0x00000000 0xff3e66b4 0x00000001 0xffbef4c4 0xffbef420: 0xffbef4cc 0x00020800 0x00000000 0x00000000 0xffbef430: 0xffbef460 0x00010738 0x00000000 0xffbef3e4 0xffbef440: 0x00000003 0xffbef4c4 0x00000004 0xffbef4cc 0xffbef450: 0x00000005 0xffbef610 0xffbef3e4 0x00000000 0xffbef460: 0x00000001 0xffbef4c4 0xffbef4cc 0x00020800 0xffbef470: 0xffbef4cc 0x00000000 0x00000000 0x00000000 dbx%
On voit très nettement sur cet exemple que la pile est assez versatile. La frame de la fonction init_string() est réutilisée, donc écrasée, par la frame de la fonction print_string(). Le persistance des données pointées par la variable str dans la fonction main() n'est que fortuite.
L'utilisation de pointeur sur une variable de pile n'est possible que dans les sous fonctions de la fonction où la variable sur pile est définie. En effet, la frame de la pile d'une fonction n'est persistante QUE tant que l'on n'est pas encore ressorti de la fonction. |
strcpy, strcat, sprintf ..., problème
de pointeur (pointeur libéré mais pas remis à 0
Le dépassement de mémoire (leak ou fuite) peut avoir des conséquences très diverses et aléatoires. Elles n'ont pas toujours les mêmes conséquences en fonction de l'ampleur de la fuite et de l'endroit où se trouve la mémoire.
Dans ce premier exemple, nous utiliserons deux variables globales statiques afin de mieux mettre en valeur le problème de fuite. La pemière sera la celle utiliser pour le débordement, et la seconde, placée juste derrière dans la mémoire, servira a "éponger" le débordement.
% cat > bss_leak.c #include <stdio.h> #include <string.h> static char buff[5]; static char oops[5]; int main(void) { strcpy(buff, "0123"); *oops = 0; printf("buff = \"%s\"\noops = \"%s\"\n", buff, oops); strcpy(oops, "oops"); printf("buff = \"%s\"\noops = \"%s\"\n", buff, oops); strcpy(buff, "01234"); printf("buff = \"%s\"\noops = \"%s\"\n", buff, oops); strcpy(buff, "012345"); printf("buff = \"%s\"\noops = \"%s\"\n", buff, oops); return 0; } ^D % cc -g bss_leak.c -o bss_leak
Vérifions que les variables sont bien définies dans le segment .bss :
% elfdump bss_leak | egrep "buff|oops" [11] 0x00020a5d 0x00000005 OBJT GLOB D 0 .bss $XAhCA_K7TOEBklT.oops [19] 0x00020a58 0x00000005 OBJT GLOB D 0 .bss $XAhCA_K7TOEBklT.buff [53] 0x00020a5d 0x00000005 OBJT GLOB D 0 .bss $XAhCA_K7TOEBklT.oops [61] 0x00020a58 0x00000005 OBJT GLOB D 0 .bss $XAhCA_K7TOEBklT.buff [11] $XAhCA_K7TOEBklT.oops [19] $XAhCA_K7TOEBklT.buff
Le lancement de bss_leak donne :
bss_leak buff = "0123" oops = "" buff = "0123" oops = "oops" buff = "01234" oops = "" buff = "012345" oops = "5"
L'écriture dans buff dépasse la taille définie pour buff. Comme oops est localisé à la suite de buff, les octets surnuméraires se retrouvent dans oops.
% dbx bss_leak Reading bss_leak Reading ld.so.1 Reading libc.so.1 Reading libdl.so.1 Reading libc_psr.so.1 dbx% stop in main (2) stop in main dbx% run Running: bss_leak (process id 12257) stopped in main at line 8 in file "bss_leak.c" 8 strcpy(buff, "0123"); dbx% p &buff &buff = 0x20a58 dbx% p &oops &oops = 0x20a5d dbx% ex 0x20a58/3 0x00020a58: buff : 0x00000000 0x00000000 0x00000000 dbx% next stopped in main at line 9 in file "bss_leak.c" 9 *oops = 0; dbx% ex 0x20a58/3 0x00020a58: buff : 0x30313233 0x00000000 0x00000000 La chaine buff contient "0123" et est terminée par un caractère nul dbx% next stopped in main at line 10 in file "bss_leak.c" 10 printf("buff = \"%s\"\noops = \"%s\"\n", buff, oops); dbx% next buff = "0123" oops = "" stopped in main at line 11 in file "bss_leak.c" 11 strcpy(oops, "oops"); dbx% next stopped in main at line 12 in file "bss_leak.c" 12 printf("buff = \"%s\"\noops = \"%s\"\n", buff, oops); dbx% ex 0x20a58/3 0x00020a58: buff : 0x30313233 0x006f6f70 0x73000000 la chaine oops contient maintenant "oops" et est terminuée par un caractère nul dbx% next next buff = "0123" oops = "oops" stopped in main at line 13 in file "bss_leak.c" 13 strcpy(buff, "01234"); dbx% next stopped in main at line 14 in file "bss_leak.c" 14 printf("buff = \"%s\"\noops = \"%s\"\n", buff, oops); dbx% ex 0x20a58/3 0x00020a58: buff : 0x30313233 0x34006f70 0x73000000 le caractère nul de la chaine buff est positionné sur le premier caractère de la chaine oops, qui semble maintenant vide dbx% next buff = "01234" oops = "" stopped in main at line 15 in file "bss_leak.c" 15 strcpy(buff, "012345"); dbx% next stopped in main at line 16 in file "bss_leak.c" 16 printf("buff = \"%s\"\noops = \"%s\"\n", buff, oops); dbx% ex 0x20a58/3 0x00020a58: buff : 0x30313233 0x34350070 0x73000000 la chaine buff contient maintenant "012345", mais le 5 et le caractère nul son situés sur les 2 premiers caractères de la chaine oops dbx% next buff = "012345" oops = "5" stopped in main at line 17 in file "bss_leak.c" 17 return 0;
Pour des fuites sur le tas, on peut avoir soir des données corrompues (affichage de valeurs aléatoires, chaines bizarres) pour des fuites de petites tailles à l'intérieur de zones allouées pour des structures par exemple.
Quand la taille de la fuite dépasse la taille réelle du block, il faut alors s'attendre à de gros ennuis. En effet, la longeur de zone allouée peut se trouver modifiée, ou encore, la zone suivante peut se retrouver corrompue (pointeur vers la zone suivante). Si le bloc suivant est un block libre, le pointeur sur le block libre suivant sera donc aléatoire et risque de faire planter le malloc suivant. Sinon, les problèmes risquent d'arriver lors du free() des blocks suivants ou en core lors de l'utilisation des données du block en cours, ou suivant.
Symptomes:
Affichage de chaines de caractères erronnés
Données invalides, corrompues ou aléatoires
boucles trop longues/trop courtes
Plantage sur free()
Plantage sur malloc()
Plantage pendant l'adressage d'un élément d'une structure
Plantage à un endroit donné avec ou sans débugger
Plantage à un endroit donné durant l'execution et absence de plantage sous débugger ou plantage à un autre endroit sous débugger
Bus error
Problème d'alignement
Segmentation fault
1
voir "Linux Kernel Hacker's Guide" à l'url http://en.tldp.org/LDP/khg/HyperNews/get/khg.html
ou encore "the Linux Kerneli" http://en.tldp.org/LDP/khg/HyperNews/get/khg.html
2Brian W. Kernigan et Dennis M. Richie, dans "The C Programming Language » font état au chapitre 2.2, page 34 de la première édition, 1978, d'une taille de 9bits pour les « char » sur les machines de type Honeywell 6000
3Le type « long long » a été introduit dans la version ANSI C99 des spécification ANSI pour le langage C
4Ces adresses sont données à titre d'exemple. Elle peuvent varier d'un programme à un autre et d'une machine à une autre.
4Cette fonctionnalité n'était pas présente à la base, mais prévue (cf. Brian W. Kernighan & Dennis M. Richie dans « The C Programming Language » paragraphe 14.1 de la page 209 de première édition 1978):
« Other opterations, such as assigning from or to it or passing it as a parameter, draw an error message. In the future, it is expected that these operations, but not necessarily others, will be allowed »
« Les autres opérations, comme l'assignation depuis ou vers une structure, ou le passage d'une structure en paramètre [d'une fonction] génèrent un message d'erreur. Il est vraissemblable que ces opérations, mais pas nécessairement d'autres, viendront à être autorisées »
Cette fonctionnalité est apparue avec le standard ANSI C89..