Menu

calloc et realloc en C : mémoire mise à zéro et agrandissement d'un tableau

calloc vous donne de la mémoire mise à zéro et multiplie la taille sans risque ; realloc agrandit un bloc que vous avez déjà rempli. Cette page couvre les deux, l'idiome du pointeur temporaire qui évite la fuite d'un realloc échoué, et les cas où calloc bat malloc plus memset.

Cette page contient des éditeurs exécutables - modifiez, exécutez et voyez la sortie instantanément.

malloc répond à une question : donne-moi tant d'octets. Deux compagnons dans <stdlib.h> répondent aux questions qui viennent ensuite - donne-moi tant d'octets, remis à zéro (calloc), et j'ai besoin que le bloc que j'ai déjà soit plus grand (realloc).

calloc : nombre, taille et zéros

void *calloc(size_t count, size_t size);

Deux arguments au lieu d'un, et le résultat est rempli d'octets nuls.

C'est le cas classique : un histogramme ou un tableau de comptage où chaque case doit partir de zéro. Avec malloc, il vous faudrait une boucle ; calloc le fait dans le cadre de l'allocation, souvent gratuitement car le système d'exploitation distribue déjà des pages neuves pré-remises à zéro.

L'argument du débordement

La séparation en deux arguments n'est pas cosmétique. Considérez un nombre lu depuis un fichier :

size_t n = huge_value_from_input;

int *a = malloc(n * sizeof *a);     /* le produit peut boucler */
int *b = calloc(n, sizeof *b);      /* tenu de detecter le debordement */

Si n * sizeof *a dépasse ce qu'un size_t peut contenir, le produit repasse à un petit nombre, malloc réussit avec un bloc minuscule, et chaque écriture suivante part bien au-delà de la fin. La norme oblige calloc à échouer et à renvoyer NULL à la place. Quand une taille vient de l'extérieur de votre programme, cette vérification vaut la peine.

Ce que « mis à zéro » veut dire exactement

calloc écrit des octets nuls. Pour les entiers et les caractères, c'est la valeur zéro, ce que vous voulez. Pour les pointeurs et les flottants, tous-bits-à-zéro est un pointeur nul et 0.0 sur toutes les plateformes courantes, mais la norme C ne le promet pas. Le code qui doit être strictement portable affecte ces valeurs explicitement.

calloc contre malloc + memset

int *a = calloc(n, sizeof *a);

int *b = malloc(n * sizeof *b);
if (b != NULL) {
    memset(b, 0, n * sizeof *b);
}

Ils produisent le même résultat. Préférez calloc : c'est une ligne, il fait la vérification de débordement, et pour de gros blocs il peut éviter de toucher la mémoire du tout. Préférez malloc seul quand vous allez de toute façon écraser chaque octet - remettre à zéro un mégaoctet que vous allez remplir immédiatement est du pur gaspillage.

realloc : changer la taille

void *realloc(void *p, size_t newsize);

realloc renvoie un bloc de newsize octets dont le contenu correspond à l'ancien bloc jusqu'à la plus petite des deux tailles. Il peut étendre le bloc sur place, ou en allouer un nouveau, copier et libérer l'ancien. Vous ne pouvez pas savoir lequel, donc le seul pointeur utilisable ensuite est celui qu'il a renvoyé.

L'idiome du pointeur tmp

Les trois lignes autour de tmp expliquent à elles seules la réputation de cette fonction. Le raccourci tentant est cassé :

/* BUG: fuite du bloc d'origine quand realloc echoue */
p = realloc(p, newsize);
if (p == NULL) {
    return;
}

Quand realloc échoue, il renvoie NULL et laisse le bloc d'origine alloué et inchangé. Affecter le résultat directement dans p écrase le seul pointeur vers ce bloc, qui ne pourra donc jamais être libéré - une fuite, et qui survient exactement au moment où la mémoire est déjà rare. Affectez à tmp, testez tmp, puis validez :

void *tmp = realloc(p, newsize);
if (tmp == NULL) {
    /* p est toujours valide ; gerez l'echec comme il convient a l'appelant */
    return 0;
}
p = tmp;

Deux autres comportements à connaître : realloc(NULL, n) se comporte exactement comme malloc(n), ce qui permet à une fonction de croissance de gérer son premier appel sans cas particulier. Et l'agrandissement laisse les nouveaux octets non initialisés - seul l'ancien contenu est préservé.

Agrandir un tableau : le motif standard

Voici le motif que presque tous les conteneurs C utilisent - une longueur, une capacité, et un doublement quand elles se rejoignent.

Deux choix de conception portent tout le poids ici.

Doubler, pas ajouter un. Croître d'une quantité fixe fait recopier tout le tableau à chaque ajout, donc remplir n éléments coûte environ copies d'octets. Le doublement rend les copies assez rares pour que chaque ajout coûte un temps constant en moyenne. Le départ cap == 0 ? 4 : cap * 2 gère le premier ajout sans branche séparée, car realloc(NULL, n) n'est rien d'autre que malloc.

La structure possède une allocation. v.data est libéré exactement une fois, par celui qui possède la structure. Écrire cette règle de propriété - dans un commentaire, ou en appariant chaque vec_init avec un vec_free - est ce qui empêche un conteneur grandissant de fuir en passant de fonction en fonction.

Rétrécir

realloc avec une taille plus petite rogne un bloc, ce qui est utile après avoir lu une quantité inconnue de données dans un tampon surdimensionné :

Un rétrécissement échoué n'est pas une erreur qui justifie d'abandonner : l'ancien bloc, plus grand, est toujours valide et toujours correct. C'est le seul cas où ignorer l'échec est le bon choix - mais notez que cela passe quand même par tmp, car la règle de ne pas écraser le pointeur vivant ne change pas.

Lequel choisir

Ce que vous voulezUtilisez
Des octets, contenu sans importancemalloc(n * sizeof *p)
Un nombre d'éléments, remis à zérocalloc(n, sizeof *p)
Un nombre venant d'une entrée non fiablecalloc, pour la vérification de débordement
Le bloc que vous avez, plus grand ou plus petitrealloc via un pointeur tmp
La première allocation d'une boucle de croissancerealloc(NULL, n) - pas de cas particulier

Tous les quatre sont libérés par le même free, exactement une fois.

Erreurs courantes

  • p = realloc(p, n) - fuite en cas d'échec. Passez toujours par tmp.
  • Garder un ancien pointeur dans le bloc. Après realloc, les données peuvent avoir bougé, donc tout pointeur interne sauvegardé ou adresse dérivée d'un indice est périmé. Les indices survivent à un déplacement ; les pointeurs non.
  • Supposer que les nouveaux octets sont à zéro. Seul calloc met à zéro. realloc laisse la zone d'agrandissement non initialisée.
  • calloc(n * size, 1) - cela jette la vérification de débordement pour laquelle la forme à deux arguments existe.
  • Libérer l'ancien pointeur après un realloc réussi. realloc l'a déjà libéré ; un second free est une double libération.

Questions fréquentes

Quelle est la différence entre calloc et malloc en C ?

Deux différences. calloc(n, size) prend le nombre et la taille d'un élément séparément et vérifie que leur produit ne déborde pas, alors que malloc(n * size) peut boucler silencieusement. Et calloc remplit le bloc de zéros, là où malloc le laisse contenir ce qui s'y trouvait avant.

Comment fonctionne realloc en C ?

realloc(p, newsize) renvoie un bloc de la nouvelle taille avec votre contenu existant préservé jusqu'à la plus petite des deux tailles. Il peut agrandir le bloc sur place ou le déplacer, donc le pointeur renvoyé est le seul que vous pouvez continuer d'utiliser - l'ancien peut déjà être libéré.

Pourquoi faut-il affecter realloc à un pointeur temporaire ?

Parce que p = realloc(p, n) écrase p avec NULL quand l'appel échoue, et le bloc d'origine reste alloué sans que rien ne pointe dessus - une fuite. Affectez d'abord à un tmp, testez-le contre NULL, et seulement ensuite écrivez p = tmp.

Quand utiliser calloc plutôt que malloc ?

Quand vous avez réellement besoin de mémoire à zéro (un tableau de compteurs, une structure dont les champs doivent partir vides, un tampon que vous remplirez partiellement) ou quand la taille est un nombre multiplié par une taille d'élément potentiellement grande. Si vous allez de toute façon écraser chaque octet, malloc évite le travail de mise à zéro.

Coddy programming languages illustration

Apprendre à coder avec Coddy

COMMENCER