Dernière modification : 26/05/2020 à 16:19
L'allocation dynamique permet d'utiliser de la mémoire en dehors de la pile (dans la zone appellée tas). L'interet principal est que la durée de vie de cette mémoire est indépendante de la pile. Ainsi, une fonction peut réserver de la mémoire et se terminer, la mémoire reste disponnible.
Exemple : on voudrait une fonction responsable de creer un tableau de taille donnée en paramètre. On pourrait être tenté d'écrire
int * creer_tableau(int taille)
{
int tab[taille];
return tab;
}
Ce code est faux pour deux raisons :
int tab[2]; /* ok */
#define TRUC 2
int tab[TRUC]; /* ok */
int n = 2;
int tab[n]; /* pas ok ! */
int * creer_tableau(int taille)
{
int * tab = ... /* appel à une fonction d'allocation */
return tab;
}
Pour manipuler la mémoire allouée sur le tas, les fonctions ont donc besoins de pointeurs, qui seront eux des variables de pile normales. Les fonctions d'allocation, comme malloc() ou calloc() vont donc renvoyer des adresses mémoires. A charge du programmeur de sauvegarder ces adresses dans des pointeurs.
Voici un exemple avec malloc() :
int * creer_tableau(int taille)
{
int * tab = (int*) malloc(sizeof(int)*taille);
return tab;
}
malloc() prend en paramètre la taille en octet que l'on souhaite allouer. Ici on veut taille entiers, il faut donc commencer par calculer la taille en octer d'un entier, grace à l'opérateur sizeof(), et la multiplier par notre variable taille qui est le nombre d'entier que l'on veut.
malloc() renvoie un pointeur générique, noté avec le type spécial void*
Pour permettre au compilateur de vérifier que l'affectation int * tab = ...
est correcte au niveau du typage,
il est donc d'usage de caster le résultat de malloc, d'où le int*
entre parenthèses.
Il existe une autre fonction d'allocation, appelée calloc(), plus spécifiquement dédié à l'allocation de tableau. Celle ci prend deux paramètres : le nombre de cases et la taille en octet d'une case. De plus les valeurs du tableau sont initialisées à 0.
Il existe enfin une troisième fonction, realloc() permettant de modifier la taille d'une zone mémoire précédemment allouée. Attention, un appel à realloc provoque potentiellement une copie de la zone mémoire lorsqu'il n'est pas possible d'étendre la zone mémoire considérée sans la déplacer.
Le principal danger est de perdre les adresses. En effet, pour libérer la mémoire, ces adresses sont nécessaires. Par exemple :
void une_fonction()
{
int * tab = creer_tableau(100);
... /* utilisation du tableau */
} /* fin de la fonction */
Ici lorsque la fonction est terminée, sa pile est libérée, et plus aucune variable accessible sur la pile ne contient l'adresse du tableau (qui est sur la tas). Il n'est donc plus possible de libérer la mémoire. Il faut soit que la fonction libère la mémoire en ajoutant la ligne :
free(tab);
soit qu'elle renvoie l'adresse du tableau afin qu'il soit libéré dans une autre fonction plus tard.
Le deuxième danger est que les fonctions d'allocation peuvent échouer (par exemple lorsqu'il n'y a pas assez de mémoire disponible. Dans ce cas elles renvoient une valeur de pointeur spéciale notée NULL (constante définie dans stdlib.h). Essayer d'accéder à l'adresse NULL provoque toujours un segmentation fault. Et la, à charge du programmeur de retrouver d'où vient l'erreur... Afin de lui faciliter la vie, il faut donc prendre l'habitude de toujours tester le retour des fonctions d'allocations. On écrira donc :
int * creer_tableau(int taille)
{
int * tab = (int*) malloc(sizeof(int)*taille);
if(tab==NULL){
fprintf(stderr,"probleme d'allocation dans creer_tableau");
exit(1);
}
return tab;
}
exit() est une fonction de stdlib.h permettant de mettre fin au programme immédiatement.
Attention avec sizeof(), comme indiqué plus haut, il ne s'agit pas d'une fonction mais d'un opérateur, même si la syntaxe ressemble à un appel de fonction. Il faut lui mettre dans les parenthèse le nom d'un type. Si vous mettez une variable, ca marche quand même et ca renvoie la taille du type, ce comportement peut préter à confusion.
Exemple :
void f(int * tab)
{
printf("%d\n",sizeof(tab));
}
int main()
{
int tab[10];
f(tab);
return 0;
}
Qu'affiche ce programme, pourquoi ?
valgrind ./a.out
: cet utilitaire permet de tracer les allocations /
libération de mémoire, et vous dit si vous avez bien tout libéré. Commentez les appels à free() dans votre code et restestez.char * s = concat("sa","lut");
printf("%s\n",s);
doit afficher salut Le résultat sera alloué avec un malloc(). Il faudra donc penser à faire à ajouter un free()...
int** alloue(int n, int m);
qui alloue un tableau de
char à deux dimensions de n lignes et m colones.void libere(int** tab, int n, int m);
qui libère la mémoire associée.char** grid
Grid* allocate_grid(int n, int m);
qui :free_grid(Grid *);
qui libère toute la mémoire occupée par la grille (le tableau à deux dimensions puis la variable de type Grid.Grid*
à la place de char grille[NBL][NBC+1]
int nbl,nbc;
size_t size_buf=0;
FILE * stream;
char * buf=NULL;
Grid * g;
int i,j;
stream = ...
nbl = count_nb_line(stream); /* fonction a écrire */
rewind(...); /* pour remettre la position de lecture du fichier au début, voir la manpage */
nbc = getline(&buf,&size_buf,stream);
if(nbc==-1){ /* erreur, le fichier est certainement mal formé */ ...}
else nbc--; /* getline compte le retour à la ligne */
g=allocate_grid(nbl,nbc);
copy(line_tmp,g->grid[0]); /* fonction a écrire, qui ne copie que les caractères que l'on veut (pas \0 ni \n) */
for (i=1;i<nbl;i++)
{
int size_tmp = getline(&buf,&size_buf,stream);
if(size_tmp!=nbc+1){
/* il y a un probleme, il faut quitter le programme */
...
}
copy(buf,g->grid[i]);
}
free(buf);