retour




Gestion de la mémoire en C

Notice

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.

Introduction

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.

(Table des matières)


La mémoire

La mémoire physique de l'ordinateur

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.


Espace mémoire des programmes C

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.


Figure 1 :  utilisation de la mémoire pour chaque processus.

Zone programme

Tout d'abord la zone de "programme", découpée en plusieurs segments dont :

Le tas (heap)

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).

Le mémoire libre

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.

Zone de liens dynamiques

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 ...).

La pile (stack)

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

#include 

int 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]


La mémoire C

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 :
  • La mémoire d'un processus est assimilable à un espace contigu,
  • Cet espace est organisé en différents groupes fonctionnels,
  • Les données d'un programme ne sont pas toutes stockées au même endroit en fonction de leur portée et de leur mode de réservation.

(Table des matières)


Le tas

Exploration du tas

principes d'un allocateur memoire

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.

Allocateur minimaliste

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.

Allocateur standards

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.

La pile

Notion de type

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.

(Table des matières)


Types simples

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:
  • D'un point de vue pratique, le type des données en mémoire est assimilable à la taille des données
  • Il ne faut jamais préjuger de la taille d'un type, mais utiliser sizeof()
  • Tous les pointeurs ont la même taille sur une architecture donnée. Cette taille est cohérente avec la taille du bus mémoire.


(Table des matières)


Alignement

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:
  • La mémoire est adressable de plusieurs manières différentes.
  • Lorsque l'architecture impose des contraintes d'alignement, l'adresse d'une donnée est un multiple de la taille des données adressées si cette taille est inférieure à la taille du bus mémoire, ou multiple de la taille du bus .
  • Nous avons vu que le typage constituait un canevas d'interprêtation de la mémoire. Réciproquement, n'importe quelle zone mémoire peut être interprètée par un type ou un autre pour peu que l'alignement soit respecté.


(Table des matières)


Types composés

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:
  • L'ordre des données définies dans une structure influe sur la taille de la mémoire utilisée afin de respecter les contraintes d'alignement (même si l'architecture n'en possède a priori pas).
  • De même que les types simples, les types complexes constituent un canevas d'interprêtation de la mémoire. Réciproquement, toute zone de mémoire respectant les contraintes d'alignement peut être interprêtée par une structure.

(Table des matières)


Tableaux

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 tableau ne connait pas sa propre taille de manière a apriori.
  • il n'est pas possible d'assigner un tableau à un autre tableau.
  • Comme nous le verrons au chapitre suivant, le tableau est un pointeur déguisé.

(Table des matières)


Notion de pointeur

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 */
   

(Table des matières)


Pointeurs et tableaux

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));

(Table des matières)


Pointeurs et structures

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"".


(Table des matières)


Pointeurs sur fonctions

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.


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"".


(Table des matières)


Désignation des données

La désignation de données peut s'effectuer de plusieurs manières par valeur, par adresse

Désignation par valeur

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;

(Table des matières)


Désignation par adresse

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


(Table des matières)


Passages de paramètres

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.


(Table des matières)


Exemple récapitulatif

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

(Table des matières)


Comment gérer la mémoire?

Allocation sur la pile

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.

Allocation dynamique

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.

(Table des matières)


Manipulation dynamique de la mémoire

Allocation de la mémoire: malloc()

Prototype:

    #include <stdlib.h>
	void *malloc(size_t size);

Comportement:

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:
  • Il est impératif de toujours vérifier que le pointeur alloué est valide.
  • Plus généralement, il est toujours nécessaire de tester un pointeur passé en argument.
  • Il ne faut jamais préjuger du contenu d'une zone mémoire après son allocation par malloc
  • Il est nécessaire d'initialiser convenablement les zones de mémoire allouées

(Table des matières)


la fonction calloc()

Prototype :
    #include <stdlib.h>
    void *calloc(size_t nmemb, size_t size);
Comportement :

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).

(Table des matières)


Désallocation de la mémoire: free()

Prototype :

    #include <stdlib.h>
    void free(void *ptr);

Comportement :

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; }

(Table des matières)


Réallocation de la mémoire : realloc()

Prototype :

    #include <stdlib.h>
    void *realloc(void *ptr, size_t size);

Comportement :

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;
    }  

(Table des matières)


Fonctions annexes: memset(), memcpy(), memmove()

Initialisation d'une zone memoire: memset()

Prototype:
    #include <string.h>
    void *memset(void *s, int c, size_t n);

Comportement:

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).

(Table des matières)


Copie de zone mémoire : memcpy()

Prototype:
    #include <string.h>
    void *memcpy(void *dest, const void *src, size_t n);
Comportement:

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:

(Table des matières)


Déplacement de zone mémoire: memmove()

Prototype:
    #include <string.h>
    void *memmove(void *dest, const void *src, size_t n);
Comportement:

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.


(Table des matières)


Gestion des chaines de caractères:

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.


Rappels

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:

strcpy()

Prototype:
    #include <string.h>
    char *strcpy(char *dest, const char *src);

Comportement:

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 # ...)


strncpy()

Prototype:
    #include <string.h>
    char *strncpy(char *dest, const char *src, size_t n);
Comportement:

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;
    }

(Table des matières)


strcat()

Prototype:
    #include <string.h>
    char *strcat(char *dest, const char *src);
Comportement:

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.


(Table des matières)


sprintf() et snprintf()

Prototype
    #include <stdio.h>
    int sprintf(char *str, const char *format, ...);
Comportement

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).

(Table des matières)


Règles de base

(Table des matières)


Erreurs liées à la mauvaise utilisation de la mémoire

Déréférencement d'un pointeur null

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é.

Passage d'un argument par valeur

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

Utilisation d'une variable non initialisée

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.

Adressage sur la pile

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.

Dépassement de l'espace alloué (leak)

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.

fuite sur le segment .bss

fuite de petite taille

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;

fuite sur le tas

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.

fuite sur la pile

free() sans malloc()

malloc() sans free()

(ou allocation de mémoire par une fonction sans désallocation -strdup()-),


Adressage d'une zone réallouée

Adressage d'une zone non alignée

(pour les architectures avec contraintes fortes d'alignement).


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

(Table des matières)


Notes

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..

(Table des matières)


Références

The C Programming Language, Brian W. Kernigan et Dennis M. Richie, ISBN 0131101633, 1978, Prentice-Hall
LE LANGAGE C, NORME ANSI, Brian W. Kernigan et Dennis M. Richie, ISBN 2100051164, 2ème édition, 1997, Dunod.

(Table des matières)


Liens Débugger mémoire