Question

J'écris en C depuis à peine quelques semaines et je n'ai pas pris le temps de trop m'inquiéter pour malloc () . Récemment, cependant, un de mes programmes a renvoyé une chaîne de visages joyeux au lieu des valeurs vrai / faux que je m'y attendais.

Si je crée une structure comme celle-ci:

typedef struct Cell {
  struct Cell* subcells;
} 

et ensuite l'initialiser comme ceci

Cell makeCell(int dim) {
  Cell newCell;

  for(int i = 0; i < dim; i++) {
    newCell.subcells[i] = makeCell(dim -1);
  }

  return newCell; //ha ha ha, this is here in my program don't worry!
}

Est-ce que je vais finir par accéder à des visages heureux stockés en mémoire quelque part, ou peut-être écrire sur des cellules déjà existantes, ou quoi? Ma question est la suivante: comment C alloue-t-il de la mémoire alors que je n’ai pas réellement affecté la quantité de mémoire appropriée à malloc ()? Quelle est la valeur par défaut?

Était-ce utile?

La solution

Il n'y a pas de valeur par défaut pour votre pointeur. Votre pointeur indiquera ce qu'il stocke actuellement. Comme vous ne l'avez pas initialisé, la ligne

newCell.subcells[i] = ...

Accède efficacement à une partie incertaine de la mémoire. Rappelez-vous que les sous-cellules [i] sont équivalentes à

*(newCell.subcells + i)

Si le côté gauche contient des données parasites, vous finirez par ajouter i à une valeur de données erronées et à accéder à la mémoire à cet emplacement incertain. Comme vous l'avez dit correctement, vous devrez initialiser le pointeur pour qu'il pointe vers une zone mémoire valide:

newCell.subcells = malloc(bytecount)

Après quelle ligne, vous pouvez accéder à autant d'octets. En ce qui concerne les autres sources de mémoire, il existe différents types de stockage qui ont tous leur utilité. Le type que vous obtenez dépend de votre type d'objet et de la classe de stockage que vous indiquez au compilateur.

  • malloc renvoie un pointeur sur un objet sans type. Vous pouvez faire pointer un pointeur sur cette région de la mémoire et le type de l'objet deviendra effectivement le type du type d'objet pointé. La mémoire n'est initialisée à aucune valeur et l'accès est généralement plus lent. Les objets ainsi obtenus sont appelés objets alloués .
  • Vous pouvez placer des objets globalement. Leur mémoire sera initialisée à zéro. Pour les points, vous obtiendrez des pointeurs NULL, pour les flotteurs, vous obtiendrez également un zéro correct. Vous pouvez compter sur une valeur initiale correcte.
  • Si vous avez des variables locales mais utilisez le spécificateur de classe de stockage static , vous aurez la même règle de valeur initiale que pour les objets globaux. La mémoire est généralement allouée de la même manière que les objets globaux, mais ce n'est en aucun cas une nécessité.
  • Si vous avez des variables locales sans spécificateur de classe de stockage ou avec auto , votre variable sera allouée sur la pile (même si cela n’a pas été défini par C, c’est ce que les compilateurs font pratiquement bien sûr ). Vous pouvez prendre son adresse, auquel cas le compilateur devra omettre les optimisations, comme le mettre dans des registres bien sûr.
  • Les variables locales utilisées avec le spécificateur de classe de stockage register sont marquées comme ayant un stockage spécial. En conséquence, vous ne pouvez plus prendre son adresse. Dans les compilateurs récents, il n'est normalement plus nécessaire d'utiliser register , en raison de leurs optimiseurs sophistiqués. Si vous êtes vraiment expert, vous pouvez en tirer des résultats si vous l'utilisez, cependant.

Les objets ont des durées de stockage associées qui peuvent être utilisées pour afficher les différentes règles d'initialisation (ils ne définissent formellement que la durée de vie des objets au moins). Les objets déclarés avec auto et enregistreur ont une durée de stockage automatique et ne sont pas initialisés. Vous devez les initialiser explicitement si vous voulez qu’ils contiennent une valeur. Si vous ne le faites pas, ils contiendront ce que le compilateur a laissé sur la pile avant leur début de vie. Les objets alloués par malloc (ou une autre fonction de cette famille, comme calloc ) ont une durée de stockage allouée. Leur stockage n’est pas non plus initialisé . Une exception est l'utilisation de calloc , auquel cas la mémoire est initialisée à zéro ("réel" zéro. C'est-à-dire tous les octets 0x00, indépendamment de toute représentation de pointeur NULL). Les objets déclarés avec static et les variables globales ont une durée de stockage statique. Leur mémoire est initialisée à zéro et convient à leur type respectif. Notez qu'un objet ne doit pas avoir de type, mais le seul moyen d'obtenir un objet sans type consiste à utiliser le stockage alloué. (Un objet en C est une "région de stockage").

Alors qu'est-ce que c'est? Voici le code fixe. Parce qu'une fois que vous avez alloué un bloc de mémoire, vous ne pouvez plus récupérer le nombre d'éléments que vous avez alloué, il est préférable de toujours stocker ce compte quelque part. J'ai ajouté une variable dim à la structure qui enregistre le nombre.

Cell makeCell(int dim) {
  /* automatic storage duration => need to init manually */
  Cell newCell;

  /* note that in case dim is zero, we can either get NULL or a 
   * unique non-null value back from malloc. This depends on the
   * implementation. */
  newCell.subcells = malloc(dim * sizeof(*newCell.subcells));
  newCell.dim = dim;

  /* the following can be used as a check for an out-of-memory 
   * situation:
   * if(newCell.subcells == NULL && dim > 0) ... */
  for(int i = 0; i < dim; i++) {
    newCell.subcells[i] = makeCell(dim - 1);
  }

  return newCell;
}

Maintenant, les choses ressemblent à ceci pour dim = 2:

Cell { 
  subcells => { 
    Cell { 
      subcells => { 
        Cell { subcells => {}, dim = 0 }
      }, 
      dim = 1
    },
    Cell { 
      subcells => { 
        Cell { subcells => {}, dim = 0 }
      }, 
      dim = 1
    }
  },
  dim = 2
}

Notez qu'en C, il n'est pas nécessaire que la valeur de retour d'une fonction soit un objet. Aucun stockage n'est requis pour exister. Par conséquent, vous n'êtes pas autorisé à le changer. Par exemple, ce qui suit n’est pas possible:

makeCells(0).dim++

Vous aurez besoin d'une "fonction libre". c'est libre la mémoire allouée à nouveau. Parce que le stockage des objets alloués n'est pas libéré automatiquement. Vous devez appeler free pour libérer cette mémoire pour chaque pointeur sous-cellules de votre arborescence. C'est un exercice pour vous d'écrire ça:)

Autres conseils

Réponse courte: Il ne vous est pas attribué.

Réponse un peu plus longue: le pointeur sous-cellules n'est pas initialisé et peut pointer n'importe où . Ceci est un bogue et vous ne devriez jamais le permettre.

Encore plus longue réponse: des variables automatiques sont allouées sur la pile, des variables globales sont allouées par le compilateur et occupent souvent un segment spécial ou peuvent se trouver dans le tas. Les variables globales sont initialisées à zéro par défaut. Les variables automatiques n'ont pas de valeur par défaut (elles obtiennent simplement la valeur trouvée en mémoire) et le programmeur est responsable de s'assurer qu'elles ont de bonnes valeurs de départ (bien que de nombreux compilateurs essaieront de vous renseigner lorsque vous oublierez).

La variable newCell de votre fonction est automatique et n'est pas initialisée. Vous devriez réparer ça pronto. Donnez à newCell.subcells une valeur explicite rapidement ou pointez-la sur NULL jusqu'à ce que vous lui allouiez de l'espace. De cette façon, vous générerez une violation de segmentation si vous essayez de la déréférencer avant de lui allouer de la mémoire.

Pire encore, vous retournez une Cell par valeur, mais vous l'attribuez à un Cell * lorsque vous essayez de remplir le tableau sous-cellules . . Renvoyez un pointeur à un objet attribué au segment de mémoire ou attribuez la valeur à un objet attribué localement.

Un idiome habituel pour cela aurait la forme quelque chose comme

Cell* makeCell(dim){
  Cell *newCell = malloc(sizeof(Cell));
  // error checking here
  newCell->subcells = malloc(sizeof(Cell*)*dim); // what if dim=0?
  // more error checking
  for (int i=0; i<dim; ++i){
    newCell->subCells[i] = makeCell(dim-1);
    // what error checking do you need here? 
    // depends on your other error checking...
  }
  return newCell;
}

bien que je vous ai laissé quelques problèmes à résoudre ..

Et notez que vous devez garder trace de tous les bits de mémoire qui devront éventuellement être désalloués ...

Tout ce qui n'est pas alloué sur le tas (via malloc et des appels similaires) est alloué sur la pile. De ce fait, tout ce qui est créé dans une fonction particulière sans être malloc 'sera détruit à la fin de la fonction. Cela inclut les objets retournés; lorsque la pile est déroulée après l'appel d'une fonction, l'objet renvoyé est copié dans l'espace qui lui est réservé sur la pile par la fonction appelant.

Avertissement: si vous souhaitez renvoyer un objet contenant des pointeurs sur d'autres objets, assurez-vous que les objets pointés sont créés sur le tas, et mieux encore, créez cet objet sur le tas, à moins qu'il ne soit pas destiné à survivre à la fonction dans laquelle il est créé.

  

Ma question est la suivante: comment C alloue-t-il de la mémoire alors que je n’ai pas encore malloc () la quantité de mémoire appropriée? Quelle est la valeur par défaut?

Ne pas allouer de mémoire. Vous devez explicitement le créer sur la pile ou de manière dynamique.

Dans votre exemple, les sous-cellules pointent vers un emplacement non défini , qui est un bogue. Votre fonction devrait renvoyer un pointeur sur une structure de cellule à un moment donné.

  

Est-ce que je vais finir par accéder à des visages heureux stockés en mémoire quelque part, ou peut-être écrire sur des cellules déjà existantes, ou quoi?

Vous avez de la chance d'avoir un visage heureux. Un de ces jours malheureux, il aurait pu nettoyer votre système;)

  

Ma question est la suivante: comment C alloue-t-il de la mémoire alors que je n’ai pas encore malloc () la quantité de mémoire appropriée?

Ce n'est pas. Cependant, lorsque vous définissez Cell newCell, le pointeur subCells est initialisé sur une valeur résiduelle. Ce qui peut être un 0 (auquel cas vous auriez un crash) ou un nombre entier assez grand pour lui donner l’impression d’une adresse mémoire réelle. Dans de tels cas, le compilateur serait heureux de récupérer la valeur qui y réside et de vous la restituer.

  

Quelle est la valeur par défaut?

C’est le comportement si vous n’initialisez pas vos variables. Et votre fonction makeCell semble un peu sous-développée.

Il existe en réalité trois sections dans lesquelles des éléments peuvent être alloués - data, stack & amp; tas.

Dans le cas que vous mentionnez, il serait alloué sur la pile. Le problème avec l'allocation de quelque chose sur la pile est que cela n'est valable que pour la durée de la fonction. Une fois que votre fonction est revenue, cette mémoire est récupérée. Donc, si vous renvoyez un pointeur sur quelque chose alloué sur la pile, ce pointeur sera invalide. Si vous retournez cependant l'objet réel (pas un pointeur), une copie de l'objet sera automatiquement créée pour que la fonction appelante l'utilise.

Si vous l'aviez déclarée en tant que variable globale (par exemple dans un fichier d'en-tête ou en dehors d'une fonction), elle serait allouée dans la section de données de la mémoire. La mémoire de cette section est allouée automatiquement au démarrage de votre programme et désallouée automatiquement à la fin.

Si vous allouez quelque chose sur le tas en utilisant malloc (), cette mémoire est valable aussi longtemps que vous le souhaitez - jusqu'à ce que vous appeliez free (), date à laquelle elle est libérée. Cela vous donne la possibilité d'allouer et de désallouer de la mémoire selon vos besoins (par opposition à l'utilisation de globals où tout est alloué à l'avance et libéré uniquement à la fin du programme).

Les variables locales sont "affectées". sur la pile. La pile est une quantité de mémoire préallouée pour contenir ces variables locales. Les variables cessent d'être valides à la sortie de la fonction et seront écrasées par ce qui va suivre.

Dans votre cas, le code ne fait rien car il ne renvoie pas votre résultat. En outre, un pointeur sur un objet de la pile cessera également d'être valide à la fermeture de la portée. Par conséquent, dans votre cas précis (vous semblez créer une liste chaînée), vous devrez utiliser malloc ().

Je vais faire semblant d'être l'ordinateur ici en lisant ce code ...

typedef struct Cell {
  struct Cell* subcells;
}

Cela me dit:

  • Nous avons un type de structure appelé Cell
  • Il contient un pointeur appelé sous-cellules
  • Le pointeur devrait être sur quelque chose de type struct Cell

Cela ne me dit pas si le pointeur se dirige vers une cellule ou un tableau de cellules. Lorsqu'une nouvelle cellule est créée, la valeur de ce pointeur est indéfinie jusqu'à ce qu'une valeur lui soit affectée. C'est une mauvaise nouvelle d'utiliser des pointeurs avant de les définir.

Cell makeCell(int dim) {
  Cell newCell;

Nouvelle structure de cellule, avec un pointeur de sous-cellules non défini. Cela ne fait que réserver un petit morceau de mémoire à appeler newCell, c'est-à-dire la taille d'une structure de cellule. Cela ne change pas les valeurs qui étaient dans cette mémoire - elles pourraient être n'importe quoi.

  for(int i = 0; i < dim; i++) {
    newCell.subcells[i] = makeCell(dim -1);

Afin d’obtenir newCell.subcells [i], un calcul est effectué pour compenser les sous-cellules de i, puis c’est déréférencé . Concrètement, cela signifie que la valeur est extraite de cette adresse mémoire. Prenons, par exemple, i == 0 ... Nous allons alors déréférencer le pointeur de sous-cellules lui-même (pas de décalage). Puisque les sous-cellules ne sont pas définies, cela pourrait être n'importe quoi. Littéralement n'importe quoi! Donc, cela demanderait une valeur quelque part complètement aléatoire dans la mémoire. Il n'y a aucune garantie de quoi que ce soit avec le résultat. Il peut imprimer quelque chose, il peut tomber en panne. Cela ne devrait certainement pas être fait.

  }

  return newCell;
}

Chaque fois que vous travaillez avec un pointeur, il est important de vous assurer qu'il est défini sur une valeur avant de le déréférencer. Encouragez votre compilateur à vous avertir, car de nombreux compilateurs modernes peuvent intercepter ce genre de chose. Vous pouvez également donner aux pointeurs des valeurs par défaut comme 0xdeadbeef (ouais! C'est un nombre en hexadécimal, c'est aussi un mot, donc ça a l'air drôle) pour qu'ils se démarquent. (L'option% p pour printf est utile pour afficher les pointeurs, car elle constitue une forme brute de débogage. Les programmes du débogueur peuvent aussi très bien les montrer.)

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top