web-dev-qa-db-fra.com

Allocation / désallocation de mémoire?

J'ai étudié l'allocation de mémoire récemment et je suis un peu confus quant aux bases. Je n'ai pas pu envelopper ma tête autour de choses simples. Que signifie allouer de la mémoire? Ce qui se produit? J'apprécierais des réponses à l'une de ces questions:

  1. Où est la "mémoire" qui est allouée?
  2. Qu'est-ce que cette "mémoire"? L'espace dans un tableau? Ou autre chose?
  3. Que se passe-t-il exactement lorsque cette "mémoire" est allouée?
  4. Que se passe-t-il exactement lorsque la mémoire est désallouée?
  5. Cela m'aiderait aussi vraiment si quelqu'un pouvait répondre à ce que malloc fait dans ces lignes C++:

    char* x; 
    x = (char*) malloc (8);
    

Merci.

36
Isaac

Le modèle de mémoire

Le standard C++ a un modèle de mémoire . Il tente de modéliser la mémoire d'un système informatique de manière générique. La norme définit qu'un octet est une unité de stockage dans le modèle de mémoire et que la mémoire est composée d'octets (§1.7):

L'unité de stockage fondamentale dans le modèle de mémoire C++ est l'octet. [...] La mémoire disponible pour un programme C++ est constituée d'une ou plusieurs séquences d'octets contigus.

Le modèle objet

La norme fournit toujours un modèle d'objet . Cela spécifie qu'un objet est une région de stockage (il est donc composé d'octets et réside en mémoire) (§1.8):

Les constructions d'un programme C++ créent, détruisent, référencent, accèdent et manipulent des objets. Un objet est une région de stockage.

Alors voilà. La mémoire est l'endroit où les objets sont stockés. Pour stocker un objet en mémoire, la région de stockage requise doit être allouée.

Fonctions d'allocation et de désallocation

La norme fournit deux fonctions d'allocation de portée globale déclarées implicitement:

void* operator new(std::size_t);
void* operator new[](std::size_t);

La manière dont ceux-ci sont mis en œuvre n'est pas la préoccupation de la norme. Tout ce qui compte, c'est de renvoyer un pointeur vers une région de stockage avec le nombre d'octets correspondant à l'argument passé (§3.7.4.1):

La fonction d'allocation tente d'allouer la quantité de stockage demandée. S'il réussit, il doit renvoyer l'adresse du début d'un bloc de stockage dont la longueur en octets doit être au moins aussi grande que la taille demandée. Il n'y a aucune contrainte sur le contenu du stockage alloué au retour de la fonction d'allocation.

Il définit également deux fonctions de désallocation correspondantes:

void operator delete(void*);
void operator delete[](void*);

Qui sont définis pour désallouer le stockage précédemment alloué (§3.7.4.2):

Si l'argument donné à une fonction de désallocation dans la bibliothèque standard est un pointeur qui n'est pas la valeur de pointeur nulle (4.10), la fonction de désallocation doit désallouer le stockage référencé par le pointeur, rendant invalides tous les pointeurs faisant référence à n'importe quelle partie du stockage désalloué .

new et delete

En règle générale, vous ne devriez pas avoir besoin d'utiliser directement les fonctions d'allocation et de désallocation car elles ne vous fournissent que de la mémoire non initialisée. À la place, en C++, vous devez utiliser new et delete pour allouer dynamiquement des objets. Une nouvelle expression obtient le stockage pour le type demandé en utilisant l'une des fonctions d'allocation ci-dessus, puis initialise cet objet d'une manière ou d'une autre. Par exemple, new int() allouera de l'espace pour un objet int puis l'initialisera à 0. Voir §5.3.4:

Une nouvelle expression obtient le stockage de l'objet en appelant une fonction d'allocation (3.7.4.1).

[...]

Une nouvelle-expression qui crée un objet de type T initialise cet objet [...]

Dans le sens opposé, delete appellera le destructeur d'un objet (le cas échéant) puis désallouera le stockage (§5.3.5):

Si la valeur de l'opérande de suppression-expression n'est pas une valeur de pointeur nulle, la suppression-expression invoquera le destructeur (le cas échéant) pour l'objet ou les éléments du tableau en cours de suppression.

[...]

Si la valeur de l'opérande de suppression-expression n'est pas une valeur de pointeur nulle, la suppression-expression appellera une fonction de désallocation (3.7.4.2).

Autres allocations

Cependant, ce ne sont pas les seules façons dont le stockage est alloué ou désalloué. De nombreuses constructions du langage nécessitent implicitement une allocation de stockage. Par exemple, donner une définition d'objet, comme int a;, Nécessite également du stockage (§7):

Une définition entraîne la réservation de la quantité de stockage appropriée et toute initialisation appropriée (8.5).

Bibliothèque standard C: malloc et free

De plus, l'en-tête <cstdlib> Apporte le contenu de la bibliothèque standard stdlib.h C, qui comprend les fonctions malloc et free. Ils sont également définis, par la norme C, pour allouer et désallouer de la mémoire, tout comme les fonctions d'allocation et de désallocation définies par la norme C++. Voici la définition de malloc (C99 §7.20.3.3):

void *malloc(size_t size);
Description
La fonction malloc alloue de l'espace à un objet dont la taille est spécifiée par size et dont la valeur est indéterminée.
Retours
La fonction malloc renvoie soit un pointeur nul, soit un pointeur vers l'espace alloué.

Et la définition de free (C99 §7.20.3.2):

void free(void *ptr);
Description
La fonction free provoque la désallocation de l'espace pointé par ptr, c'est-à-dire qu'il est disponible pour une allocation ultérieure. Si ptr est un pointeur nul, aucune action ne se produit. Sinon, si l'argument ne correspond pas à un pointeur précédemment renvoyé par la fonction calloc, malloc ou realloc, ou si l'espace a été désalloué par un appel à free ou realloc, le comportement n'est pas défini.

Cependant, il n'y a jamais de bonne excuse pour utiliser malloc et free en C++. Comme décrit précédemment, C++ a ses propres alternatives.


Réponses aux questions

Donc pour répondre directement à vos questions:

  1. Où est la "mémoire" qui est allouée?

    La norme C++ s'en fiche. Il dit simplement que le programme a de la mémoire composée d'octets. Cette mémoire peut être allouée.

  2. Qu'est-ce que cette "mémoire"? L'espace dans un tableau? Ou autre chose?

    En ce qui concerne la norme, la mémoire n'est qu'une séquence d'octets. C'est délibérément très générique, car la norme essaie seulement de modéliser des systèmes informatiques typiques. Vous pouvez, pour la plupart, le considérer comme un modèle du RAM de votre ordinateur.

  3. Que se passe-t-il exactement lorsque cette "mémoire" est allouée?

    L'allocation de mémoire rend une région de stockage disponible pour une utilisation par le programme. Les objets sont initialisés dans la mémoire allouée. Tout ce que vous devez savoir, c'est que vous pouvez allouer de la mémoire. L'allocation réelle de mémoire physique à votre processus a tendance à être effectuée par le système d'exploitation.

  4. Que se passe-t-il exactement lorsque la mémoire est désallouée?

    La désallocation de mémoire précédemment allouée rend cette mémoire indisponible pour le programme. Il devient un stockage désalloué.

  5. Cela m'aiderait aussi vraiment si quelqu'un pouvait répondre à ce que malloc fait dans ces lignes C++:

    char* x; 
    x = (char*) malloc (8);
    

    Ici, malloc alloue simplement 8 octets de mémoire. Le pointeur qu'il renvoie est converti en un char* Et stocké dans x.

60
Joseph Mansfield

1) Où est la "mémoire" qui est allouée?

Ceci est complètement différent en fonction de votre système d'exploitation, de l'environnement de programmation (gcc vs Visual C++ vs Borland C++ vs toute autre chose), de l'ordinateur, de la mémoire disponible, etc. En général, la mémoire est allouée à partir de ce que l'on appelle le tas, région de mémoire qui attend autour pour vous d'utiliser. Il utilisera généralement votre RAM disponible. Mais il y a toujours des exceptions. Pour l'essentiel, tant qu'il nous donne de la mémoire, d'où il vient n'est pas une grande préoccupation. Il existe des types spéciaux de mémoire, tels que la mémoire virtuelle, qui peuvent ou non être réellement dans RAM à tout moment donné et peuvent être déplacés vers votre disque dur (ou un périphérique de stockage similaire) si vous manquez de mémoire réelle. Une explication complète serait très longue!

2) Qu'est-ce que cette "mémoire"? L'espace dans un tableau? Ou autre chose?

La mémoire est généralement le RAM dans votre ordinateur. S'il est utile de considérer la mémoire comme un gigantesque "tableau", elle fonctionne certainement comme une seule, alors pensez-y comme une tonne d'octets (8 valeurs de bits, un peu comme unsigned char valeurs). Il commence à un index de 0 au bas de la mémoire. Tout comme avant, cependant, il y a des tonnes d'exceptions ici et certaines parties de la mémoire peuvent être mappées sur du matériel, ou même ne pas exister du tout!

) Que se passe-t-il exactement lorsque cette "mémoire" est allouée?

À tout moment, il devrait y en avoir (nous espérons vraiment!) Qu'une partie de ce logiciel puisse être allouée. La façon dont il est alloué dépend fortement du système. En général, une région de mémoire est allouée, l'allocateur la marque comme utilisée, puis un pointeur vous est donné à utiliser qui indique au programme où se trouve dans toute la mémoire de votre système cette mémoire. Dans votre exemple, le programme trouvera un bloc consécutif de 8 octets (char) et retournera un pointeur à l'endroit où il a trouvé ce bloc après l'avoir marqué comme "en cours d'utilisation".

4) Que se passe-t-il exactement lorsque la mémoire est désallouée?

Le système marque cette mémoire comme disponible pour une nouvelle utilisation. C'est incroyablement compliqué car cela causera souvent des trous dans la mémoire. Allouez 8 octets puis 8 octets de plus, puis désallouez les 8 premiers octets et vous avez un trou. Il existe des livres entiers écrits sur la gestion de la désallocation, l'allocation de mémoire, etc. Donc, espérons que la réponse courte sera suffisante!

5) Cela m'aiderait vraiment si quelqu'un pouvait répondre à ce que fait malloc dans ces lignes C++:

VRAIMENT grossièrement, et en supposant que c'est dans une fonction (au fait, ne faites jamais cela car cela ne désalloue pas votre mémoire et provoque une fuite de mémoire):

void mysample() {
  char *x; // 1
  x = (char *) malloc(8); // 2
}

1) Il s'agit d'un pointeur réservé dans l'espace de pile local. Il n'a pas été initialisé, il indique donc tout ce que ce bit de mémoire contenait.

2) Il appelle malloc avec un paramètre de 8. Le cast laisse juste savoir à C/C++ que vous avez l'intention qu'il soit un (char *) car il retourne un (void *) ce qui signifie qu'il n'a aucun type appliqué. Ensuite, le pointeur résultant est stocké dans votre variable x.

Dans un assemblage x86 32 bits très brut, cela ressemblera vaguement à

PROC mysample:
  ; char *x;
  x = DWord Ptr [ebp - 4]
  enter 4, 0   ; Enter and preserve 4 bytes for use with 

  ; x = (char *) malloc(8);
  Push 8       ; We're using 8 for Malloc
  call malloc  ; Call malloc to do it's thing
  sub esp, 4   ; Correct the stack
  mov x, eax   ; Store the return value, which is in EAX, into x

  leave
  ret

L'allocation réelle est vaguement décrite au point 3. Malloc appelle généralement une fonction système pour cela qui gère tout le reste, et comme tout le reste ici, c'est très différent d'un OS à l'autre, d'un système à l'autre, etc.

11
Mark Ormston

1 . Où est la "mémoire" qui est allouée?

Du point de vue de la langue, cela n'est pas spécifié, et principalement parce que les petits détails n'ont souvent pas d'importance. Également C++ standard tend à pécher par excès de détails matériels, pour minimiser les restrictions inutiles (à la fois sur les plates-formes sur lesquelles les compilateurs peuvent s'exécuter et sur les optimisations possibles).

la réponse de sftrabbit donne un bon aperçu de cette fin des choses (et c'est tout ce dont vous avez vraiment besoin), mais je peux donner quelques exemples pratiques au cas où cela aiderait.

Exemple 1:

Sur un ordinateur mono-utilisateur suffisamment ancien (ou un ordinateur suffisamment petit intégré), la plupart des physiques RAM peuvent être directement disponibles pour votre programme. Dans ce scénario, appeler malloc ou new est essentiellement une comptabilité interne, permettant à la bibliothèque d'exécution de suivre les morceaux de ce RAM sont actuellement utilisés. Vous pouvez le faire manuellement, mais cela devient assez fastidieux rapidement.

Exemple 2:

Sur un système d'exploitation multitâche moderne, le physique RAM est partagé avec de nombreux processus et autres tâches, y compris les threads du noyau. Il est également utilisé pour la mise en cache du disque et la mise en mémoire tampon des E/S en arrière-plan, et est augmenté par le sous-système de mémoire virtuelle qui peut échanger des données sur le disque (ou tout autre périphérique de stockage) lorsqu'ils ne sont pas utilisés.

Dans ce scénario, appeler new peut d'abord vérifier si votre processus dispose déjà de suffisamment d'espace libre en interne, et demander plus au système d'exploitation sinon. Quelle que soit la mémoire renvoyée, elle peut être physique ou virtuelle (auquel cas physique RAM peut ne pas être assigné pour le stocker jusqu'à ce qu'il soit réellement accessible). Vous ne pouvez même pas faire la différence, au moins sans utiliser d'API spécifiques à la plate-forme, car le matériel mémoire et le noyau conspirent pour vous le cacher.

2. Qu'est-ce que cette "mémoire"? L'espace dans un tableau? Ou autre chose?

Dans l'exemple 1, c'est quelque chose comme de l'espace dans un tableau: l'adresse retournée identifie un bloc adressable de RAM physique. Même ici, RAM ne sont pas nécessairement plates ou contiguës - certaines adresses peuvent être réservées pour la ROM ou pour les ports d'E/S.

Dans l'exemple 2, c'est un index vers quelque chose de plus virtuel: l'espace d'adressage de votre processus. Il s'agit d'une abstraction utilisée pour masquer les détails de la mémoire virtuelle sous-jacente de votre processus. Lorsque vous accédez à cette adresse, le matériel de mémoire peut accéder directement à de la RAM réelle, ou il peut être nécessaire de demander au sous-système de mémoire virtuelle d'en fournir.

3. Que se passe-t-il exactement lorsque cette "mémoire" est allouée?

En général, un pointeur est retourné que vous pouvez utiliser pour stocker autant d'octets que vous le souhaitez. Dans les deux cas, malloc ou l'opérateur new fera un peu de ménage pour suivre quelles parties de l'espace d'adressage de votre processus sont utilisées et lesquelles sont gratuites.

4. Que se passe-t-il exactement lorsque la mémoire est désallouée?

Encore une fois en général, free ou delete fera un peu de ménage pour qu'ils sachent que la mémoire est disponible pour être réallouée.

Cela m'aiderait aussi vraiment si quelqu'un pouvait répondre à ce que malloc fait dans ces lignes C++:

char* x; 
x = (char*) malloc (8);

Il retourne un pointeur qui est soit NULL (s'il n'a pas pu trouver les 8 octets que vous voulez), soit une valeur non NULL.

Les seules choses que vous pouvez dire utilement à propos de cette valeur non NULL sont les suivantes:

  • il est légal (et sûr) d'accéder à chacun de ces 8 octets x[0]..x[7],
  • il est illégal (comportement indéfini) d'accéder à x[-1] ou x[8] ou en fait n'importe lequelx[i] sauf si 0 <= i <= 7
  • il est légal de comparer l'un des x, x+1, ..., x+8 (bien que vous ne puissiez pas déréférencer le dernier de ceux-ci)
  • si votre plate-forme/matériel/quoi que ce soit a des restrictions sur l'endroit où vous pouvez stocker des données en mémoire, alors x les rencontre
7
Useless

Allouer de la mémoire signifie demander de la mémoire au système d'exploitation. Cela signifie que c'est le programme lui-même qui demande de "l'espace" dans RAM quand seulement quand il en a besoin. Par exemple si vous voulez utiliser un tableau mais vous ne connaissez pas sa taille auparavant le programme s'exécute, vous pouvez faire deux choses: - déclarer et tableau [x] avec x déduit par vous, long arbitraire. Par exemple 100. Mais qu'en est-il si votre programme a juste besoin d'un tableau de 20 éléments? Vous gaspillez de la mémoire pour rien . - alors vous programmez peut malloc un tableau d'éléments x juste au moment où il connaît la taille correcte de x. Les programmes en mémoire sont divisés en 4 segments: -stack (nécessaire pour l'appel aux fonctions) -code (le code exécutable bibary) - data (variables/données globales) - tas, dans ce segment, vous trouverez la mémoire allouée. Lorsque vous décidez que vous n'avez plus besoin de la mémoire allouée, vous la restituez au système d'exploitation.

Si vous souhaitez allouer et tableau de 10 entiers, vous faites:

int * array = (int *) malloc (sizeof (int) * 10)

Et puis vous le rendez à l'OS avec free (array)

3
user2204592