INFO517-cours5
Séance 5 du Cours-TD de Programmation C.
Modèle mémoire: pile, tas, segment de code, allocation dynamique.
Les exemples vus en cours
Le code suivant affiche la chaîne abcdef en minuscules puis en majuscules: <source lang="c">
- include <stdio.h>
void maj (char s[]) { int i ; for (i=0 ; s[i]!='\0' ; i++) { char c = s[i] ; if (c>='a' && c<='z') s[i] = c+'A'-'a' ; } }
int main () { char s[] = "abcdef" ; /* tableau = pointeur ? */
puts(s) ; maj(s) ; puts(s) ; return 0 ; } </source>
Si on change la déclaration de s en <source lang="c"> char s* = "abcdef" ; </source> on obtient une erreur de segmentation. Pourquoi ?
Les tableaux ne sont pas des variables comme les autres: un tableau t doit être considéré comme un alias pour &t[0], qui n'est pas une valeur gauche (quelque chose qui va à gauche du signe =): c'est de la même nature que n+1 par exemple. Lors de la déclaration d'un tableau, son contenu (dont la taille est connue à la compilation) est alloué sur la pile. Par contraste, un pointeur est une variable entière qui contient une adresse. Lors de la déclaration <source lang="c"> char s* = "abcdef" ; </source> le tableau constant "abcdef" est alloué dans le segment de code en lecture seule et s pointe sur le premier caractère: c'est très différent.
On va mettre en évidence ce phénomène dans la suite en précisant l'emplacement mémoire des divers éléments d'un programme.
Voilà d'abord une petite bibliothèque pour afficher des adresses.
mem.h <source lang="c">
- ifndef MEM_H
- define MEM_H
- include <stdio.h>
void mem_indent (int n) ;
/********** Affichage de pointeurs **********/
void mem_addr (void *p) ; void mem_addr_ptr (void **p) ; void mem_addr_ch (char **p) ; void mem_ch (char *p) ; void mem_addr_int (int *p) ;
/********** Affichage de la pile **********/
int * mem_debut_pile ; void mem_aff_pile (int *haut) ; void mem_aff_pile_chars (int *haut) ;
- endif
</source> mem.c <source lang="c">
- include <stdio.h>
- include "mem.h"
/********** Fonctions utiles **********/
/* Affiche un caractère s'il est directement imprimable,
* un code standard s'il est connu et le code octal sinon. */
void mem_print_char (char c) { if ((c>='a' && c<='z') || (c>='A' && c<='Z') || (c>='0' && c<='9')) printf("%4c",c) ; else switch (c) { case '.': case ',': case ';': case '!': case '?': case ':': printf("%4c",c) ; break ; case ' ': printf(" ' '") ; break ; case '\n': printf(" \\n") ; break ; case '\t': printf(" \\t") ; break ; default: printf("\\%03o",(unsigned char)c) ; } }
/* Indente avec `n' tabulations */ void mem_indent (int n) { int i ; for (i=1;i<=n;i++) putchar('\t') ; }
/********** Affichage de pointeurs **********/
/* Affiche la valeur d'un pointeur (c'est-à-dire l'adresse de la case vers
* laquelle il pointe. */
void mem_addr (void *p) { printf("%10u == %8x\n", (unsigned int) p, (unsigned int) p) ; }
/* Affiche l'adresse et la valeur d'un pointeur. */ void mem_addr_ptr (void **p) { printf("%10u == %8x -> %10u == %8x\n", (unsigned int) p, (unsigned int) p, (unsigned int) *p, (unsigned int) *p); }
/* Affiche l'adresse et la valeur d'un pointeur sur `char',
* puis l'affiche comme une chaîne. */
void mem_addr_ch (char **p) { printf("%10u == %8x -> %10u == %8x -> %s\n", (unsigned int) p, (unsigned int) p, (unsigned int) *p, (unsigned int) *p, *p) ; }
/* Affiche la valeur d'un pointeur sur `char',
* puis l'affiche comme une chaîne. */
void mem_ch (char *p) { printf("%10u == %8x -> %s\n", (unsigned int) p, (unsigned int) p, p) ; }
/* Affiche l'adresse et la valeur d'un `int' */ void mem_addr_int (int *p) { printf("%10u == %8x -> %11d\n", (unsigned int) p, (unsigned int) p, *p) ; }
/* Affiche l'adresse et la valeur d'un `int',
* avec les conversions non-signée et hexa. */
void mem_addr_ints (int *p) { printf("%10u == %8x -> %11d == %10u == %8x\n", (unsigned int) p, (unsigned int) p, *p, (unsigned int) *p, (unsigned int) *p) ; }
/* Variante de la précédente avec la conversion en caractères. */ void mem_addr_chars (int *p) { char *s ; int i ; printf("%10u == %8x -> %11d == %8x == {", (unsigned int) p, (unsigned int) p, *p, (unsigned int) *p) ; s=(char *)p ; mem_print_char(s[0]) ; for (i=1;i<sizeof(int);++i) { printf(","); mem_print_char(s[i]) ; } printf("}\n") ; }
/********** Affichage de la pile **********/
/* Le pointeur mem_debut_pile doit être initialisé à l'adresse d'une variable
* de pile */
void mem_aff_pile (int *haut) { int *p ; for (p=haut ; p<= mem_debut_pile ; p++) mem_addr_ints(p) ; }
void mem_aff_pile_chars (int *haut) { int *p ; for (p=haut ; p<= mem_debut_pile ; p++) mem_addr_chars(p) ; } </source>
Le programme suivant met en évidence l'allocation des chaînes constantes dans le segment de code, des tableaux locaux dans la pile, et l'allocation dynamique dans le tas. Le code a les adresses les plus basses. Le tas a des adresses petites et croissantes. La pile a des adresses grandes et décroissantes. <source lang="c">
- include <stdio.h>
- include <stdlib.h>
- include "mem.h"
- define TAILLE 40
- define REC 5
char tab_ext [] = "tableau externe"; char *ptr_ext = "pointeur externe";
void f (int n) { char tab_f [] = "f: tableau"; char *ptr_f = "f: pointeur"; char *ptr_all_f = malloc(TAILLE) ; snprintf(ptr_all_f,TAILLE,"f: pointeur alloué %d", n) ;
if (n<REC) { mem_indent(n), mem_addr_int(&n) ; mem_indent(n), mem_ch(tab_f) ; mem_indent(n), mem_addr_ch(&ptr_f) ; mem_indent(n), mem_addr_ch(&ptr_all_f) ; f(n+1) ; } }
int main () { char tab_main [] = "main: tableau"; char *ptr_main = "main: pointeur"; char *ptr_all_main = malloc(TAILLE) ; snprintf(ptr_all_main,TAILLE,"main: pointeur alloué") ;
mem_ch(tab_ext) ; mem_addr_ch(&ptr_ext) ; mem_ch(tab_main) ; mem_addr_ch(&ptr_main) ; mem_addr_ch(&ptr_all_main) ;
f(0) ; return 0 ; } </source>
À l'exécution du programme suivant, on remarque que les adresses de la pile décroissent plus vite que ce qu'on aurait en n'empilant que les arguments. <source lang="c">
- include <stdio.h>
- include <stdlib.h>
- include "mem.h"
- define REC 8
void g (int n) ;
void f (int n) { if (n<REC) { mem_addr_int(&n) ; g(n) ; } }
void g (int n) { if (n<REC) { mem_addr_int(&n) ; f(n+1) ; } }
int main () { int n = 0 ; mem_addr_int(&n) ; f(1) ;
return 0 ;
} </source> C'est donc qu'il y a autre chose sur la pile.
Le programme suivant affiche l'intégralité de la pile: <source lang="c">
- include <stdio.h>
- include <stdlib.h>
- include "mem.h"
- define REC 8
void g (int n) ;
void f (int n) { if (n<REC) { mem_addr_int(&n) ; g(n) ; } else { printf("Affichage de la pile :\n") ; mem_aff_pile(&n) ; } }
void g (int n) { if (n<REC) { mem_addr_int(&n) ; f(n+1) ; } }
int main () { int n = 0 ; mem_debut_pile = &n ;
mem_addr_int(&n) ; f(1) ; return 0 ;
} </source> On remarque la pile contient des pointeurs vers des emplacements dans le segment de code. Il s'agit en fait d'adresses de retour vers le code de f et g, comme mis en évidence par l'exécution du programme suivant: <source lang="c">
- include <stdio.h>
- include <stdlib.h>
- include "mem.h"
- define REC 8
void g (int n) ;
void f (int n) { if (n<REC) { g(n) ; } else { printf("Affichage de la pile :\n") ; mem_aff_pile(&n) ; } }
void g (int n) { if (n<REC) { f(n+1) ; } }
int main () { int n = 0 ; mem_debut_pile = &n ;
printf("Adresse de f : "), mem_addr((void *)f) ; printf("Adresse de g : "), mem_addr((void *)g) ; f(1) ; return 0 ; } </source> À noter que les noms de fonctions peuvent être considérés comme des pointeurs dans le segment de code. Les adresses de retour mises en évidence dans la pile correspondent à des adresses dans le code de f et g.
On revient sur le deuxième exemple en affichant la pile, et en convertissant les entiers en blocs de caractères: on retrouve les tableaux de caractères alloués sur la pile. <source lang="c">
- include <stdio.h>
- include <stdlib.h>
- include "mem.h"
- define TAILLE 40
- define REC 5
char tab_ext [] = "tableau externe"; char *ptr_ext = "pointeur externe";
void f (int n) { char tab_f [] = "f: tableau"; char *ptr_f = "f: pointeur"; char *ptr_all_f = malloc(TAILLE) ; snprintf(ptr_all_f,TAILLE,"f: pointeur alloué %d", n) ;
if (n<REC) { mem_indent(n), mem_addr_int(&n) ; mem_indent(n), mem_ch(tab_f) ; mem_indent(n), mem_addr_ch(&ptr_f) ; mem_indent(n), mem_addr_ch(&ptr_all_f) ; f(n+1) ; } else mem_aff_pile_chars(&n) ; }
int main () { int n = 0 ; char tab_main [] = "main: tableau"; char *ptr_main = "main: pointeur"; char *ptr_all_main = malloc(TAILLE) ; snprintf(ptr_all_main,TAILLE,"main: pointeur alloué") ;
mem_ch(tab_ext) ; mem_addr_ch(&ptr_ext) ; mem_ch(tab_main) ; mem_addr_ch(&ptr_main) ; mem_addr_ch(&ptr_all_main) ;
mem_debut_pile = &n ; f(0) ;
return 0 ;
} </source>