OVH Cloud OVH Cloud

comprendre malloc et realloc

47 réponses
Avatar
heron
Bonjour

Je d=E9bute avec le C depuis quelques mois =E0 mon rhytme. J'arrive au
chapitre de l'allocation dynamique. J'ai abord=E9 les tableaux uni et
multidimensionnels, les pointeurs, les fonctions.

En fait, je comprends l'utilit=E9 de malloc dans le sens ou la
r=E9servation de la m=E9moire pour un tableau peut se faire en cours
d'ex=E9cution...

Pour la saisie de texte, je voudrais savoir s'il serait possible de
saisir une cha=EEne de caract=E8re variable avec fgets ? Je pense beaucoup
=E0 utiliser realloc.

Par exemple : je r=E9serve de la m=E9moire pour un pointeur vers une
adresse de type caract=E8re, pour 1 ou 2 caract=E8res et =E0 je demande =E0
l'utilisateur de saisir du texte. A chaque saisie, je r=E9serve la place
pour un caract=E8re suppl=E9mentaire en m=E9moire avec realloc, c'est
faisable? Bizarement, je ne trouve aucun code qui correspond =E0 ce que
je recherche sur le net.

Ca ressemble =E0 peu pr=E8s, mais =E7a ne marche pas :
http://www.cppfrance.com/forum/sujet-RECUERATION-CHAINE-CARACTERE-TAILLE-VA=
RIABLE-STDIN_1405662.aspx

Autre question sans lien avec la premi=E8re :
est-ce que =E9crire char* chaine =3D 0 et char *chaine =3D 0 revient =E0 la
m=EAme chose?

Merci,

10 réponses

1 2 3 4 5
Avatar
heron
Non, je parle d'efficacite au sens algorithmique.



En effet, je vois que c'est pas simple.

J'ai repris le code de saisie des nombres avec allocations dynamique
pour qu'il ressemble à celui de saisie de la chaine de caractère du
point de vue du nombre de variable :

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
int *numbers = NULL ;
int input ;
int cnt = 0 ;

do
{
printf("nEnter an integer value (enter 0 to stop) : ") ;
scanf("%d", &input) ;
cnt++ ;
numbers = (int *) realloc(numbers, cnt * sizeof(int) ) ;

if ( numbers == NULL )
{
puts("Error (re)allocating memory") ;
exit(1) ;
}
numbers[cnt-1] = input ;

} while ( input != 0 ) ;

printf("nNbr entres : n") ;

for ( numbers-cnt ; *numbers ; numbers++ )
printf("nvaleur %d", *numbers) ;

printf("nn") ;
return 0 ;
}

int *numbers = NULL ;
char *chaine = 0 ;



C'est identique?

Sans trop savoir, ça revient à réserver un espace mémoire pour un
pointeur?

Ca reviendrait à faire appel à malloc sauf que la quantité de mémoi re
est nulle ? (à ce niveau, je ne maîtrise pas très bien mes questions
pour ce chapitre).

Jusqu'à présent il me semblait que malloc ou calloc allaient de pairs
avec realloc. C'est donc indispensable d'avoir résever un pointeur
pour faire appel à realloc peu importe la manière (soit un pointeur
vers zero ou nulle soit avec malloc) ?

Les premiers chapitres des cours trouvés sur le net qui portent sur
malloc et la mémoire dynamique ne font qu'expliquer que réserver de la
mémoire avec malloc ne se fait qu'en cours d'exécution et non de
compilation. Pour des petits programmes qui nécessitent peu de temps
de compilation, malloc n'est pas indispensable? Car ces cours
présentent malloc juste comme une déclaration. Je ne vois pas de
différence avec la déclaration statique d'un tableau (pour un petit
programme).

Ou alors, si je veux comprendre autrement, avec malloc, on réserve une
grande quantité d'espace en mémoire dans l'hypothèse ou l'utilisateur
aurait plus de données à saisir que prévues et si ce n'est pas le cas ,
on libère l'espace réservé avec free ? Ainsi il y a un gain de mémo ire
disponible? Ca serait le seul avantage autre que la durée de
compilation.

Dans le code ci-dessus, la fonction free ne fait que libérer un espace
mémoire d'un octet (sizeof(char) ) et 2 ou 4 octets ( sizeof(int) ).
Donc le remplissage et le redimensionnement du tableau se sont fait en
simultanée. C'est ce qui me semble apréciable et plus souple que
réserver dynamiquement une "grande" quantité de mémoire qu'il faudra
libérer par la suite...

Dernière question, si j'oublie la fonction free dans le code, il n'y
aucun avertissement et le code s'exécute sans rien afficher?
Avatar
espie
In article ,
heron wrote:

Non, je parle d'efficacite au sens algorithmique.
int *numbers = NULL ;
char *chaine = 0 ;



C'est identique?

Sans trop savoir, ça revient à réserver un espace mémoire pour un
pointeur?

Ca reviendrait à faire appel à malloc sauf que la quantité de mémoire
est nulle ? (à ce niveau, je ne maîtrise pas très bien mes questions
pour ce chapitre).



Non, ca revient juste a avoir de l'espace pour ton pointeur, et a l'initialiser
comme pointeur.

Je ne sais pas trop si c'est les cours que tu lis qui sont mauvais, ou que
tu ne les comprends, ou que tu reexpliques tres mal ce que tu as compris,
mais ca ne correspond a rien de ce que je connais du langage.

Jusqu'à présent il me semblait que malloc ou calloc allaient de pairs
avec realloc. C'est donc indispensable d'avoir résever un pointeur
pour faire appel à realloc peu importe la manière (soit un pointeur
vers zero ou nulle soit avec malloc) ?



Tu ne te places pas du tout au bon niveau. "aller de pair" ca ne veut rien
dire. "Indispensable de reserver" pas beaucoup plus.


Les premiers chapitres des cours trouvés sur le net qui portent sur
malloc et la mémoire dynamique ne font qu'expliquer que réserver de la
mémoire avec malloc ne se fait qu'en cours d'exécution et non de
compilation. Pour des petits programmes qui nécessitent peu de temps
de compilation, malloc n'est pas indispensable? Car ces cours
présentent malloc juste comme une déclaration. Je ne vois pas de
différence avec la déclaration statique d'un tableau (pour un petit
programme).



Non, aucun rapport.

Faut que tu trouves un cours qui t'explique le modele memoire du C.
C'est ce qui compte, et apres, les pointeurs et malloc, ca ira tout seul
(j'espere).

En tres simple, lorsque tu ecris du code tel que:

void f()
{
char t[10];
...
g(t);
}

tu definis une variable locale (t), de portee le bloc englobant, et de classe
de stockage memoire "automatique".

Ca veut dire que, dans la fonction f, tu peux utiliser sans probleme t[0]
a t[9], et que tu peux passer t en parametre a une autre fonction (comme g).

Ta variable a comme duree de vie le bloc environnant: c'est du ressort du
compilateur de s'arranger pour que tu aies de la place jusqu'a ce que f()
soit fini (runtime donc), et t est donc "vivant" pendant l'appel a g().

[En pratique, une ecrasante majorite des implementations de C utilisent une
"pile": toutes les variables automatiques sont stockees dessus. Ce que fait
le compilateur, c'est juste generer du code de prologue pour ta fonction f()
qui prend suffisamment d'espace sur la pile pour le tableau t, et du
code d'epilogue qui rend cet espace. C'est juste 2 ou 3 instructions machine]

Simple efficace, mais limite par rapport a ce que ca permet: le tableau t
"n'existe plus" lorsque tu quittes f. Et l'espace memoire associe est reutilise
aussitot, aucune possibilite de faire autrement.

Notons egalement qu'il n'y a pas de *pointeur* dans ce cas precis: c'est un
tableau, tout est gere a la compilation, il n'y a pas d'emplacement qui sert
a stocker l'adresse du tableau... au moins du point de vue de f.

par contre, pour generer l'appel a g, ben il faut l'adresse du tableau, qui
va etre transmis comme parametre, et stocke quelque part dans g.

Justement, regardons g maintenant:

void g(char *p)
{
int i;
for (i = 0; i < 10; i++)
putchar(p[i]);
}

Un parametre de fonction (a peu de choses pres, une variable locale, donc
souvent alloue sur la pile), et une deuxieme variable locale. Toujours pas
de malloc, et pourtant tout marche bien.


Lorsque tu veux de l'allocation dynamique, les choses sont un peu differentes.

char *f()
{
char *p;

p = malloc(10);

g(p);
return p;
}

J'ai p, variable locale, donc allouee automatiquement sur la pile, qui
initialement pointe n'importe ou. Apres le malloc (s'il reussit), j'ai une
zone de 10 caracteres, donc equivalente a ce qu'on avait avant.

Par contre, cette zone n'est pas invalidee par la sortie de f... ma variable
p "meurt", mais je prend bien soin de rendre le resultat du malloc a l'appelant.


Celui-ci peut par exemple faire:

char *q = f();
... diverses choses avec q.

free(q);


C'est tout l'interet de malloc/free: ca permet de gerer la duree de vie (et
la taille) de ses allocations soi-meme, au lieu de tout laisser au compilateur.
Et ca permet donc de realiser des structures de donnees de taille et de forme
variable (sans malloc/free, la gestion memoire est "cablee en dur" dans la
structure du code).

bref.
Avatar
Samuel DEVULDER
heron a écrit :

int *numbers = NULL ;
char *chaine = 0 ;



C'est identique?

Sans trop savoir, ça revient à réserver un espace mémoire pour un
pointeur?

Ca reviendrait à faire appel à malloc sauf que la quantité de mémoire
est nulle ? (à ce niveau, je ne maîtrise pas très bien mes questions
pour ce chapitre).



Je ne suis pas sur de comprendre si c'est bien le sens de ta question,
mais je pense que tu te demande si on peut passer NULL à realloc() au
lieu de lui passer un truc obtenu par malloc()/calloc() précédent. En
fait oui: realloc() accepte un pointeur NULL auquel cas il se comporte
comme malloc() de la taille passée en argument.

sam.
Avatar
heron
Merci tout le monde, jusqu'à présent, je progresse peu à peu.

Voici un code et l'extrait d'un cours d'un document pdf qui apparaît
après dans la première page sur le moteur de recherche google.

Cours écrit par Guillaume Verdier :

int main (void) {
int taille ;
int * tab ;
int i ;
printf ("Quelle est la taille du tableau ? ") ;
scanf ("%d", &taille) ;
if ((tab = malloc (taille * sizeof (int))) == NULL) {
fprintf (stderr, "Erreur a l'allocation d'un tableau
de %d elements.n",

taille) ;
return EXIT_FAILURE ;
}
printf ("Saisissez les %d elements du tableau :n", taille) ;
for (i = 0 ; i < taille ; ++i)
scanf ("%d", tab + i) ;
printf ("Le tableau contient :n") ;
for (i = 0 ; i < taille ; ++i)
printf ("%d", tab[i]) ;
putchar ('n') ;
free (tab) ;
return EXIT_SUCCESS ;
}
"Je ne pense pas que ce soit particulièrement compliqué : on demande
à l'utilisateur la taille du
tableau qu'il veut utiliser, on alloue un tableau de cette taille (on
vérifie que l'allocation s'est bien
passée), on demande à l'utilisateur de saisir les éléments du table au,
on les affiche et on libère la
mémoire. Bien entendu, on peut utiliser la « version » de malloc que
l'on veut : avec conversion
explicite ou non, avec sizeof (int) ou sizeof (*p).
On voit au passage un énorme avantage des allocations dynamiques :
autant pour une variable
seule, les allocations dynamiques paraissent un peu inutiles, autant
elles sont ici particulièrement
pratiques : en utilisant seulement des allocations statiques, on
aurait dû définir une taille maximum
de tableau, allouer (statiquement) un tableau de cette taille et
empêcher l'utilisateur d'utiliser un
tableau plus grand. Dans ce cas, on utilise, la plupart du temps,
beaucoup plus de mémoire que de
besoin, et dans certains cas l'utilisateur n'aura pas un tableau assez
grand !"

=>FIN DU COPIER COLLER<=

A mes yeux, ca ressemble à tout les cours que j'ai pu lire sur le
sujet sans être trop compliqué.

En comparaison au code plus haut dans ce post, je ne vois aucun "gros"
avantage à faire appel au code de son exemple. Je peux comprendre plus
tard que si on lit un fichier deplusieurs milliers de lignes malloc
est utile mais pour la saisie de quelques données, j'en conclu que le
code plus haut est le seul qui convient et est à la fois souple et
simple ce qui rend le code de son exemple pas très utile malgré le
fait que cet exemple est celui de nombreux cours...
Avatar
Benoit Izac
Bonjour,

le 15/05/2010 à 20:36, heron a écrit dans le message
:

En comparaison au code plus haut dans ce post, je ne vois aucun "gros"
avantage à faire appel au code de son exemple. Je peux comprendre plus
tard que si on lit un fichier deplusieurs milliers de lignes malloc
est utile mais pour la saisie de quelques données, j'en conclu que le
code plus haut est le seul qui convient et est à la fois souple et
simple ce qui rend le code de son exemple pas très utile malgré le
fait que cet exemple est celui de nombreux cours...



Le plus simple c'est de faire ce que t'a expliqué Marc : tu alloues un
tableau d'une taille arbitraire disons 16 éléments, tu lis ces 16
éléments, s'il y en a plus, tu réalloues avec 32 éléments, tu lis les 16
suivants, si nécessaire, tu passes à 64, tu lis les 32 suivants, etc.
jusqu'à ce que tu aies assez de place.

#include <stdio.h>
#include <stdlib.h>

int
main(void)
{
unsigned size = 1;
unsigned i, nb_read;
int c, *p;

if ((p = malloc(size * sizeof (*p))) == NULL)
exit(1);

i = 0;
for (;;) {
if (i == size) {
size *= 2;
printf("realloc (taille = %u)n", size);
if ((p = realloc(p, size * sizeof (*p))) == NULL)
exit(2);
}

printf("Entrez un nombre (0 pour quitter) : ");
fflush(stdout);

if (scanf("%d", &c) != 1) {
/* note que ça ne va pas marcher si on saisit 1.2 */
while (getchar() != 'n')
;
continue;
}

if (c == 0)
break;

p[i] = c;
i++;
}

nb_read = i;
if (nb_read) {
printf("Nombres saisis :n");
for (i = 0; i < nb_read; i++)
printf(" %dn", p[i]);
}

return 0;
}

--
Benoit Izac
Avatar
heron
En fait, j'aurais dû commencer dès le départ par ce post:
je me demande à quoi peut servir le code ci-dessous, également tiré
d'un chapitre sur l'allocation dynamique.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

main()
{
/* Déclarations */
char INTRO[500];
char *TEXTE[10];
int I;
/* Traitement */
for (I=0; I<10; I++)
{
fgets(INTRO, 500, stdin) ; /* (maj) dans le code original
c'était gets(INTRO) */
/* Réservation de la mémoire */
TEXTE[I] = malloc(strlen(INTRO)+1);
/* S'il y a assez de mémoire, ... */
if (TEXTE[I])
/* copier la phrase à l'adresse */
/* fournie par malloc, ... */
strcpy(TEXTE[I], INTRO);
else
{
/* sinon quitter le programme */
/* après un message d'erreur. */
printf("ERREUR: Pas assez de mémoire n");
exit(-1);
}
}
return 0;
}

Est-ce que l'auteur n'a pas oublié free(TEXTE) ?
En fait, je me demande pourquoi char *TEXTE[10] au lieu de char *TEXTE

Dans le paragraphe qui précède le code de ce post, l'auteur écrit :

"
Nous voulons lire 10 phrases au clavier et mémoriser les phrases en
utilisant un tableau de pointeurs sur char. Nous déclarons ce tableau
de pointeurs par:

char *TEXTE[10];

Pour les 10 pointeurs, nous avons besoin de 10*p octets. Ce nombre est
connu dès le départ et les octets sont réservés automatiquement. Il
nous est cependant impossible de prévoir à l'avance le nombre d'octets
à réserver pour les phrases elles-mêmes qui seront introduites lors d e
l'exécution du programme ..."
---fin copier-coller---

Est-ce qu'on peut procéder avec :
char* TEXTE = 0 ;
comme plus haut?
Avatar
Marc
Marc Espie wrote:

[realloc: capacite+=constante vs capacite*=constante]
On prefere le 2e schema dans l'enorme majorite des applications. Sur la
plupart des OS, le plus souvent, il n'y a "pas de place" apres ton tableau,
et realloc revient donc a allouer un nouveau tableau et tout copier...



Tu es peut-être un peu pessimiste. Si on fait un malloc(1) suivi de
p=realloc(p,i++) en boucle et qu'on vérifie à quelles étapes realloc
renvoie un pointeur différent, avec beaucoup d'allocateurs, on constate
une progression géométrique collant précisément à ce capacite*=constante.
Ce qui ne veut pas dire qu'il faut compter sur ce comportement, il vaut
effectivement mieux gérer soi-même le doublement de capacité en supposant
que realloc n'est pas plus efficace que malloc+memcpy+free.
Avatar
Samuel DEVULDER
heron a écrit :
En fait, j'aurais dû commencer dès le départ par ce post:
je me demande à quoi peut servir le code ci-dessous, également tiré
d'un chapitre sur l'allocation dynamique.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

main()
{
/* Déclarations */
char INTRO[500];
char *TEXTE[10];
int I;
/* Traitement */
for (I=0; I<10; I++)
{
fgets(INTRO, 500, stdin) ; /* (maj) dans le code original
c'était gets(INTRO) */
/* Réservation de la mémoire */
TEXTE[I] = malloc(strlen(INTRO)+1);
/* S'il y a assez de mémoire, ... */
if (TEXTE[I])
/* copier la phrase à l'adresse */
/* fournie par malloc, ... */
strcpy(TEXTE[I], INTRO);
else
{
/* sinon quitter le programme */
/* après un message d'erreur. */
printf("ERREUR: Pas assez de mémoire n");
exit(-1);
}
}
return 0;
}

Est-ce que l'auteur n'a pas oublié free(TEXTE) ?



non.. car TEXTE est une zone sur la pile (auto). Par contre un
free(TEXT[i]) pour i=0..9 manque. Sur un OS ou un runtime qui ne
récupère pas les trucs malloc()és à la sortie du main() tu aura une
fuite mémoire.

En fait, je me demande pourquoi char *TEXTE[10] au lieu de char *TEXTE



Il réserve 10 pointeurs sur char sur la pile, cad un tableaux de 10
adresses mémoire contenant les chaines. Chaque TEXTE[i] est une chaine
allouée sur le tas. Si tu écris char *TEXTE, alors TEXTE est une chaine
unique. Chaque TEXTE[i] est alors un seul caractère de cette chaine unique.

Est-ce qu'on peut procéder avec :
char* TEXTE = 0 ;
comme plus haut?



Presque, il te faut un pointeur sur "char*", cad un "char **TEXTE;"

Du coup comme la zone utilisée n'est pas sur la pile, mais dans le tas,
tu dois faire malloc/realloc de TEXTE à chaque nouvelle chaine lue
(c'est la meme chose que lorsque tu reallouais à chaque caractère
supplémentaire lu).

Avec le tableau de taille 10, tu n'as pas besoin (et surtout pas le
droit) de faire de malloc(), le compilo utilise la pile (en fait la zone
"auto") tout seul, mais du coup tu ne peux pas dépasser 10 eléments. Si
tu veux 100000 elements, alors tu devras déclarer un tableau auto avec
autant d'elements, ce qui signifie que le compilo allouera environ
400000 octets sur la pile, ce qui est bien trop pour certains OS où les
piles sont limitées à quelques KO (par exemple en cas d'utilisation de
threads).

sam.
Avatar
heron
Comme je découvre quelque chose de nouveau, je voudrais comprendre
comment je faisais avant:

je viens de me rendre compte que fgets pouvait agir comme realloc :

Ex :

char Txt[5] ;

printf("nEntrez une phrase : n") ;
fgets(Txt, 10, stdin) ;

Je comprends qu'il n'y a rien de dynamique dans le sens ou pour ce
bout de code, toutes les instructions, déclarations sont compilées et
rien ne se produit pendant l'exécution... Mais c'est équivalent à
realloc dans le résultat même si ça ne va pas dans la mémoire pile
'stack). Fgets place les données sur le "tas" (heap). J'espère que
c'est la bonne différence que je comprends.
Avatar
Samuel DEVULDER
heron a écrit :
Comme je découvre quelque chose de nouveau, je voudrais comprendre
comment je faisais avant:

je viens de me rendre compte que fgets pouvait agir comme realloc :



Non, absolument pas. fgets ne realloue rien du tout, il écrit dans le
tableau passé en argument.

Ex :

char Txt[5] ;



Txt est alloué sur la pile pour 5 elements.


printf("nEntrez une phrase : n") ;
fgets(Txt, 10, stdin) ;



Ici tu lis jusqu'à 10 éléments, donc nécessairement tu va déborder Txt
et ton prog planter (car après le 5eme élément de la pile, il y a des
données importantes: adresse de retour, etc qui seront corrompues).

Je comprends qu'il n'y a rien de dynamique dans le sens ou pour ce
bout de code, toutes les instructions, déclarations sont compilées et



Ben cela est aussi vrai pour tous les progs, qu'ils fassent appel à de
l'allocation de mémoire dynamique ou pas.

rien ne se produit pendant l'exécution... Mais c'est équivalent à
realloc dans le résultat même si ça ne va pas dans la mémoire pile
'stack).



Je ne comprends pas ce que tu veux dire par "equivalent à realloc()".
Rien dans la doc de fgets() ne parle de realloc ou de ses amies.

Fgets place les données sur le "tas" (heap).

Non: il place les données dans le tableau que tu lui passe et ce tableau
doit être suffisamment grand pour ne pas être débordé. Ici comme tu fais
fgets(Txt, 10, stdin), alors Txt doit au moins pouvoir contenir 10
éléments, sinon ca va planter à un moment a cause du débordement de tableau.

J'espère que
c'est la bonne différence que je comprends.



Je ne vois pas de quelle différence du veux parler. La pile, le tas tout
cela c'est de la mémoire et il n'y a rien de magique la dedans: si tu
débordes un tableau tu corromps les données autour et ton prog va planter.

sam.
1 2 3 4 5