web-dev-qa-db-fra.com

Pourquoi ou quand avez-vous besoin d'allouer dynamiquement de la mémoire en C?

L'allocation dynamique de mémoire est un sujet très important dans la programmation C. Cependant, je n'ai pas pu trouver une bonne explication de ce que cela nous permet de faire, ni pourquoi cela est nécessaire.

Ne pouvons-nous pas simplement déclarer des variables et des structures et ne jamais avoir à utiliser malloc ()?

En remarque, quelle est la différence entre:

ptr_one = (int *)malloc(sizeof(int));

et

int *ptr_one = malloc(sizeof(int));
23
user2517777

Vous devez utiliser la mémoire dynamique lorsque:

  • Vous ne pouvez pas déterminer la quantité maximale de mémoire à utiliser au moment de la compilation;
  • Vous voulez allouer un très grand objet;
  • Vous souhaitez créer des structures de données (conteneurs) sans taille supérieure fixe;

Vous ne savez pas toujours combien de mémoire vous devrez mettre de côté lors de la compilation. Imaginez le traitement d'un fichier de données (une série chronologique de températures, par exemple), où le nombre d'enregistrements dans le fichier n'est pas fixe. Vous pouvez avoir jusqu'à 10 enregistrements ou jusqu'à 100 000. Si vous souhaitez lire toutes ces données en mémoire pour les traiter, vous ne saurez pas combien de mémoire allouer avant d'avoir lu le fichier. Si le fichier est structuré de sorte que la toute première valeur soit le nombre d'enregistrements, vous pouvez faire quelque chose comme ceci:

size_t recs = 0;
double *temps = NULL;

FILE *fp = fopen ( filename, "r" );
if ( fp )
{
  if ( fscanf( fp, "%zu", &recs ) == 1 )
  {
    temps = malloc( sizeof *temps * recs );
    if ( temps )
    {
      // read contents of file into temps
    }
  }
}

Parfois, vous devez allouer un très grand objet, quelque chose comme

int ginormous[1000][1000][1000];

En supposant un entier de 4 octets, ce tableau nécessitera 4 Go. Malheureusement, les trames de pile (où les variables locales sont conservées sur la plupart des architectures) ont tendance à être beaucoup plus petites que cela, donc essayer d'allouer autant de mémoire peut conduire à une erreur d'exécution (et le fait généralement). Le pool de mémoire dynamique (alias le tas) est généralement beaucoup plus grand que la pile, et encore moins n'importe quel cadre de pile. donc pour quelque chose qui désagréable, vous auriez besoin d'écrire quelque chose comme

int (*ginormous)[1000][1000] = malloc( sizeof *ginormous * 1000 );

Il est toujours possible qu'une telle demande échoue; si votre segment est suffisamment encadré, il se peut que vous ne disposiez pas d'un seul bloc contigu suffisamment grand pour suspendre la demande. Si nécessaire, vous pouvez effectuer une allocation au coup par coup; les lignes ne seront pas nécessairement adjacentes en mémoire, mais il est plus probable que vous puissiez récupérer toute la mémoire dont vous avez besoin:

int ***ginormous = malloc( sizeof *ginormous * 1000 );
if ( ginormous )
{
  for ( size_t i = 0; i < 1000; i++ )
  {
    ginormous[i] = malloc( sizeof *ginormous[i] * 1000 );
    if ( ginormous[i] )
    {
      ginormous[i][j] = malloc ( sizeof *ginormous[i][j] * 1000 );
      if ( ginormous[i][j] )
      {
        // initialize ginormous[i][j][k]
      }
    }
  }
}

Et enfin, la mémoire dynamique vous permet de créer des conteneurs qui peuvent s'agrandir et se réduire à mesure que vous ajoutez ou supprimez des données, telles que des listes, des arbres, des files d'attente, etc. caractères (similaire au type string en C++).

21
John Bode

L'allocation dynamique est requise lorsque vous ne connaissez pas les exigences les plus défavorables pour la mémoire. Ensuite, il est impossible d'allouer statiquement la mémoire nécessaire, car vous ne savez pas de combien vous aurez besoin.

Même si vous connaissez les conditions les plus défavorables, il peut être souhaitable d'utiliser l'allocation dynamique de mémoire. Il permet à la mémoire système d'être utilisée plus efficacement par plusieurs processus. Tous les processus peuvent valider statiquement leurs besoins en mémoire dans le pire des cas, mais cela limite le nombre de processus en cours d'exécution pouvant exister sur le système. S'il n'est jamais vrai que tous les processus utilisent le pire des cas en même temps, la mémoire système est constamment sous-utilisée, ce qui représente un gaspillage de ressources.

Quant à votre question secondaire, vous ne devez pas transtyper le résultat d'un appel à malloc() en C. Cela peut masquer le bogue d'une déclaration manquante (les déclarations implicites étaient autorisées avant C.99), et entraîne comportement indéfini. Préférez toujours prendre le résultat de malloc() sans transtypage. malloc() est déclaré renvoyer void *, et en C, une conversion entre void * et un autre type de pointeur est toujours autorisé (qualificatifs de type modulo comme const).

7
jxh

En remarque, quelle est la différence entre: ptr_one = (int *)malloc(sizeof(int)) et int *ptr_one = malloc(sizeof(int))

Voir ceci .

Tout d'abord, je sais que c'est probablement une question ridicule, car l'allocation dynamique de mémoire est un sujet très important dans la programmation C. Cependant, je n'ai pas pu trouver une bonne explication de ce que cela nous permet de faire, ni pourquoi cela est nécessaire.

Le pool de mémoire (ou plus communément le tas) est très grand par rapport à la pile. Considérez ces deux exemples pour savoir pourquoi il est utile d'utiliser le pool de mémoire sur la pile:

1. Et si vous définissiez un tableau et vouliez qu'il persiste parmi plusieurs trames de pile? Bien sûr, vous pouvez la déclarer en tant que variable globale et elle sera stockée dans la section des données globales de la mémoire, mais cela deviendra encombré à mesure que votre programme s'agrandit. Vous pouvez également le stocker dans le pool de mémoire.

int *func( int k ) {
  assert( k >= 1 );

  int *ptr_block = malloc( sizeof( int ) * k );

  if ( ptr_block == NULL ) exit( EXIT_FAILURE );

  for ( int i = 0; i < k; i++ ) {
    ptr_block[ i ] = i + 1;
  }

  return ptr_block; // Valid.
}

... cela ne fonctionnerait cependant pas si vous définissiez votre tableau sur la pile. La raison en est qu'une fois un cadre de pile sauté, toutes les adresses mémoire peuvent être utilisées par un autre cadre de pile (et donc écrasées), tandis que l'utilisation de la mémoire du pool de mémoire persistera jusqu'à freed par l'utilisateur (vous ou le client) ).

2. Et si vous vouliez implémenter un tableau dynamique pour gérer la lecture d'une grande séquence arbitraire de nombres? Vous ne seriez pas en mesure de le faire en définissant votre baie sur la pile, vous auriez besoin d'utiliser le pool de mémoire. Rappelez-vous qu'il est extrêmement courant (et fortement recommandé, sauf si vous avez explicitement besoin de copier une structure) de passer un pointeur vers une structure, jamais la structure elle-même (car elles peuvent être assez grandes). Considérez cette petite implémentation d'un tableau dynamique:

struct dyn_array {
  int *arr;
  int len;
  int cap;
};

typedef struct dyn_array *DynArray;

void insert_item( int const item, DynArray dyn_arr ) {
  // Checks pre conditions.
  assert( dyn_arr != NULL );

  // Checks if the capacity is equal to the length. If so, double.
  if ( dyn_arr->cap == dyn_arr->len ) {
    dyn_arr->cap *= 2;

    DynArray new_dyn_arr = malloc( sizeof( int ) * dyn_arr->cap ); // [oo]

    // ... copy, switch pointers and free...
  }

  // ... insert, increase length, etc.
}

... en ligne [oo] remarquez que si cela était défini sur la pile, une fois cette trame de pile sautée, toutes les adresses mémoire du tableau ne seraient plus allouées. Cela signifie qu'une autre trame de pile (probablement la suivante) utilisera ces adresses mémoire (ou un sous-ensemble de celles-ci).

Remarque: À partir de mon extrait de code, ptr_block est stocké sur la pile: d'où &ptr_block est une adresse de pile, mais la valeur de ptr_block est quelque part du pool de mémoire.

2
Jacob Pollack