Quand devrais-je utiliser malloc en C et quand pas?

94

Je comprends comment fonctionne malloc (). Ma question est, je vais voir des choses comme ceci:

#define A_MEGABYTE (1024 * 1024)

char *some_memory;
size_t size_to_allocate = A_MEGABYTE;
some_memory = (char *)malloc(size_to_allocate);
sprintf(some_memory, "Hello World");
printf("%s\n", some_memory);
free(some_memory);

J'ai omis la vérification des erreurs par souci de concision. Ma question est la suivante: ne pouvez-vous pas simplement faire ce qui précède en initialisant un pointeur vers un stockage statique en mémoire? peut-être:

char *some_memory = "Hello World";

À quel moment avez-vous réellement besoin d'allouer vous-même la mémoire au lieu de déclarer / initialiser les valeurs que vous devez conserver?

randombits
la source
5
Re: J'ai omis la vérification des erreurs par souci de brièveté - malheureusement, trop de programmeurs omettent la vérification des erreurs parce qu'ils ne réalisent pas qu'ils malloc()peuvent échouer!
Andrew

Réponses:

132
char *some_memory = "Hello World";

crée un pointeur vers une constante de chaîne. Cela signifie que la chaîne "Hello World" sera quelque part dans la partie en lecture seule de la mémoire et que vous avez juste un pointeur vers elle. Vous pouvez utiliser la chaîne en lecture seule. Vous ne pouvez pas y apporter de modifications. Exemple:

some_memory[0] = 'h';

Demande des ennuis.

D'autre part

some_memory = (char *)malloc(size_to_allocate);

alloue un tableau de caractères (une variable) et some_memory pointe vers cette mémoire allouée. Maintenant, ce tableau est à la fois en lecture et en écriture. Vous pouvez maintenant faire:

some_memory[0] = 'h';

et le contenu du tableau devient "bonjour le monde"

codaddict
la source
19
Juste pour clarifier, autant j'aime cette réponse (je vous ai donné +1), vous pouvez faire la même chose sans malloc () en utilisant simplement un tableau de caractères. Quelque chose comme: char some_memory [] = "Bonjour"; some_memory [0] = 'W'; fonctionnera également.
randombits
19
Vous avez raison. Vous pouvez le faire. Lorsque vous utilisez malloc (), la mémoire est allouée dynamiquement au moment de l'exécution, vous n'avez donc pas besoin de fixer la taille du tableau au moment de la compilation.Vous pouvez également la faire croître ou la réduire en utilisant realloc () Aucune de ces choses ne peut être faite lorsque vous faites: char some_memory [] = "Bonjour"; Ici, même si vous pouvez modifier le contenu du tableau, sa taille est fixe. Donc, selon vos besoins, vous utilisez l'une des trois options: 1) pointeur vers char const 2) tableau alloué dynamiquement 3) taille fixe, tableau alloué au temps de compilation.
codaddict
Pour souligner qu'il est en lecture seule, vous devez écrire. const char *s = "hi";N'est-ce pas réellement requis par la norme?
Till Theis
@Till, non car vous avez déclaré un pointeur initialisé sur l'adresse de base de la chaîne littérale "hi". s peut être réattribué parfaitement légalement pour pointer vers un caractère non const. Si vous voulez un pointeur constant vers une chaîne en lecture seule, vous avez besoin deconst char const* s;
Rob11311
38

Pour cet exemple exact, malloc est de peu d'utilité.

La principale raison pour laquelle malloc est nécessaire est lorsque vous avez des données qui doivent avoir une durée de vie différente de la portée du code. Votre code appelle malloc dans une routine, stocke le pointeur quelque part et finit par appeler gratuitement dans une routine différente.

Une raison secondaire est que C n'a aucun moyen de savoir s'il reste suffisamment d'espace sur la pile pour une allocation. Si votre code doit être 100% robuste, il est plus sûr d'utiliser malloc car votre code peut alors savoir que l'allocation a échoué et la gérer.

R Samuel Klatchko
la source
4
Les cycles de vie de la mémoire, et la question connexe de savoir quand et comment la désallouer, sont un problème important avec de nombreuses bibliothèques et composants logiciels courants. Ils ont généralement une règle bien documentée: "Si vous passez un pointeur vers cette une de mes routines, vous devez l'avoir malléée. Je vais en garder une trace et la libérer quand j'en aurai fini. " Une source courante de bogues désagréables est de transmettre un pointeur vers la mémoire allouée statiquement à une telle bibliothèque. Lorsque la bibliothèque tente de le libérer (), le programme se bloque. J'ai récemment passé beaucoup de temps à corriger un bug comme celui écrit par quelqu'un d'autre.
Bob Murphy
Êtes-vous en train de dire que le seul moment où malloc () est utilisé pratiquement, c'est quand il y a un segment de code qui sera appelé plusieurs fois au cours de la vie du programme qui sera appelé plusieurs fois et doit être `` nettoyé '', car malloc () est accompagné de free ()? Par exemple, dans un jeu comme la roue de la fortune, où après avoir deviné et placé l'entrée dans un tableau de caractères désigné, que le tableau de taille malloc () peut être libéré pour la prochaine estimation?
Smith suffira
La durée de vie des données est en effet la vraie raison d'utiliser malloc. Supposons qu'un type de données abstrait est représenté par un module, il déclare un type de liste et des routines pour ajouter / supprimer des éléments de la liste. Ces valeurs d'éléments doivent être copiées dans une mémoire allouée dynamiquement.
Rob11311
@Bob: ces méchants bugs, rendent la convention selon laquelle l'allocateur libère de la mémoire bien supérieure, après tout vous pouvez la recycler. Supposons que vous allouiez de la mémoire avec calloc pour améliorer la localité des références, ce qui expose la nature cassée de ces bibliothèques, car vous devez appeler free juste une fois pour tout le bloc. Heureusement, je n'ai pas eu à utiliser de bibliothèques qui spécifient que la mémoire est «malloc-ed». Ce n'est pas une tradition POSIX et serait très probablement considéré comme un bogue. S'ils «savent» que vous devez utiliser malloc, pourquoi la routine de la bibliothèque ne le fait-elle pas pour vous?
Rob11311
17

malloc est un outil formidable pour allouer, réallouer et libérer de la mémoire au moment de l'exécution, par rapport aux déclarations statiques comme votre exemple hello world, qui sont traitées au moment de la compilation et ne peuvent donc pas être modifiées en taille.

Malloc est donc toujours utile lorsque vous traitez des données de taille arbitraire, comme la lecture du contenu de fichiers ou le traitement des sockets et que vous ne connaissez pas la longueur des données à traiter.

Bien sûr, dans un exemple trivial comme celui que vous avez donné, malloc n'est pas le "bon outil magique pour le bon travail", mais pour les cas plus complexes (créer un tableau de taille arbitraire à l'exécution par exemple), c'est le seul moyen de aller.

Moritz
la source
7

Si vous ne connaissez pas la taille exacte de la mémoire que vous devez utiliser, vous avez besoin d'une allocation dynamique ( malloc). Un exemple peut être lorsqu'un utilisateur ouvre un fichier dans votre application. Vous devrez lire le contenu du fichier en mémoire, mais bien sûr vous ne connaissez pas la taille du fichier à l'avance, car l'utilisateur sélectionne le fichier sur place, au moment de l'exécution. Donc, fondamentalement, vous avez besoin malloclorsque vous ne connaissez pas à l'avance la taille des données avec lesquelles vous travaillez. Au moins c'est l'une des principales raisons d'utiliser malloc. Dans votre exemple avec une chaîne simple dont vous connaissez déjà la taille au moment de la compilation (plus vous ne voulez pas la modifier), cela n'a pas beaucoup de sens de l'allouer dynamiquement.


Légèrement hors sujet, mais ... vous devez faire très attention à ne pas créer de fuites de mémoire lors de l'utilisation malloc. Considérez ce code:

int do_something() {
    uint8_t* someMemory = (uint8_t*)malloc(1024);

    // Do some stuff

    if ( /* some error occured */ ) return -1;

    // Do some other stuff

    free(someMemory);
    return result;
}

Voyez-vous ce qui ne va pas avec ce code? Il existe une instruction de retour conditionnelle entre mallocet free. Cela peut sembler correct au début, mais réfléchissez-y. S'il y a une erreur, vous reviendrez sans libérer la mémoire que vous avez allouée. C'est une source courante de fuites de mémoire.

Bien sûr, c'est un exemple très simple, et il est très facile de voir l'erreur ici, mais imaginez des centaines de lignes de code parsemées de pointeurs, de mallocs, de frees et de toutes sortes de gestion des erreurs. Les choses peuvent devenir très compliquées très vite. C'est l'une des raisons pour lesquelles je préfère de loin le C ++ moderne au C dans les cas applicables, mais c'est un tout autre sujet.

Donc, chaque fois que vous utilisez malloc, assurez-vous toujours que votre mémoire est aussi susceptible d'être freed que possible.

adam10603
la source
Excellent exemple! Chemin à parcourir ^ _ ^
Musa Al-hassy
6
char *some_memory = "Hello World";
sprintf(some_memory, "Goodbye...");

est illégal, les littéraux de chaîne le sont const.

Cela allouera un tableau de caractères de 12 octets sur la pile ou globalement (selon l'endroit où il est déclaré).

char some_memory[] = "Hello World";

Si vous souhaitez laisser de la place pour d'autres manipulations, vous pouvez spécifier que le tableau doit être plus grand. (Cependant, ne mettez pas 1 Mo sur la pile.)

#define LINE_LEN 80

char some_memory[LINE_LEN] = "Hello World";
strcpy(some_memory, "Goodbye, sad world...");
printf("%s\n", some_memory);
éphémère
la source
5

Une des raisons pour lesquelles il est nécessaire d'allouer la mémoire est si vous souhaitez la modifier au moment de l'exécution. Dans ce cas, un malloc ou un tampon sur la pile peut être utilisé. L'exemple simple d'affectation de "Hello World" à un pointeur définit la mémoire qui "généralement" ne peut pas être modifiée au moment de l'exécution.

Mark Wilkins
la source