Utiliser l'allocation de mémoire Stack vs Heap en C++
Cet article expliquera plusieurs méthodes d’utilisation de l’allocation de mémoire en pile et en tas en C++.
Différence entre la pile et la mémoire de tas en C++
Lorsque nous voulons discuter des concepts de mémoire, il est préférable de penser en termes de systèmes sur lesquels les programmes utilisateur les plus courants s’exécutent. La plupart des programmes utilisateur sont exécutés dans un environnement de système d’exploitation, qui gère les ressources matérielles pour nous et prend en charge diverses tâches qui seraient trop complexes ou inefficaces pour qu’un programme utilisateur puisse les gérer. L’une de ces tâches consiste à gérer directement la mémoire matérielle. Ainsi, presque tous les systèmes d’exploitation fournissent des structures et des fonctions spéciales pour interagir avec la mémoire matérielle. Deux concepts courants dans les structures de mémoire fournies par le système d’exploitation sont la pile et le tas.
Une pile est une région de mémoire réservée à chaque programme en cours d’exécution dans le système, et elle fonctionne de manière LIFO. À savoir, lorsque le programme commence à exécuter la fonction main
, cette dernière obtient son cadre de pile (un sous-ensemble de la mémoire de pile), où les variables locales et les adresses de retour d’appel de fonction sont stockées automatiquement. Une fois que le main
appelle une autre fonction, un nouveau cadre de pile est créé après le précédent de manière continue. Le cadre de pile le plus récent stockera les objets locaux pour la fonction correspondante et lorsqu’il reviendra, cette mémoire sera inoccupée.
Notez que la taille de la pile est fixée par défaut sur la plupart des systèmes, mais peut être personnalisée dans une certaine mesure si l’utilisateur a des besoins particuliers. La limitation de taille de la mémoire de pile la rend adaptée aux objets petits et principalement temporaires. Par exemple, la taille de pile par défaut du programme utilisateur dans le système d’exploitation Linux est de 8 Mo. Il peut être plus petit qu’une seule photo JPEG qu’un programme peut avoir besoin de traiter, de sorte que l’utilisateur doit utiliser cet espace avec prudence. Les variables déclarées dans l’extrait de code suivant sont toutes stockées dans la mémoire de la pile. En règle générale, chaque variable locale est allouée sur la pile si elle n’a pas de spécificateurs spéciaux comme static
ou volatile
.
#include <iostream>
using std::cout;
using std::endl;
int main() {
int var1;
int var2 = 123;
int arr1[4] = {1, 2, 3, 4};
int var3 = var2;
cout << var1 << endl;
cout << var2 << endl;
cout << var3 << endl;
return EXIT_SUCCESS;
}
Production:
0
123
123
D’autre part, il existe une région de mémoire appelée - tas (également appelée free store
), où de gros objets peuvent être stockés et des allocations effectuées manuellement par le programmeur pendant l’exécution. Ces deux caractéristiques rendent la mémoire de tas dynamique par nature, car sa taille n’a pas besoin d’être déterminée au moment de la compilation ou à n’importe quel moment pendant l’exécution du programme. Le programme peut appeler des fonctions spéciales et demander les allocations au système d’exploitation. Notez que la mémoire de tas peut sembler infinie du point de vue du programme car elle n’est pas limitée à appeler une autre fonction d’allocation pour demander plus de mémoire. Cependant, le système d’exploitation gère la mémoire pour tous les processus en cours d’exécution ; et il peut refuser de nouvelles allocations lorsqu’il n’y a plus de mémoire physique disponible.
Le système de mémoire dans le système d’exploitation est assez complexe et nécessite une compréhension de divers concepts spécifiques au système d’exploitation/au matériel, nous ne couvrons donc que le strict minimum sur les mémoires de tas et de pile dans ce sujet. La gestion manuelle de la mémoire heap en langage C++ peut se faire à l’aide des opérateurs new
/delete
ou des fonctions malloc
/free
. Notez que ces fonctions fonctionnent de manière similaire, où l’utilisateur spécifie généralement le nombre d’octets à allouer et renvoie l’adresse où la même quantité de mémoire a été allouée. Le programmeur peut par conséquent opérer sur la région de mémoire donnée selon les besoins.
L’exemple de code suivant illustre plusieurs cas d’allocation des différents objets sur la mémoire de tas. Une caractéristique importante de la gestion manuelle de la mémoire est de renvoyer la région de mémoire allouée au système d’exploitation lorsqu’elle n’est plus nécessaire. Cette dernière opération se fait à l’aide d’appels delete
/free
correspondant à leurs homologues d’attribution. Si le programme ne libère pas la mémoire inutile, il y a un risque que le système d’exploitation manque de mémoire, et le programme peut être tué en conséquence. Notez, cependant, que le problème précédent devrait principalement se produire dans les programmes de longue durée, et ils sont caractérisés comme des bogues de fuite de mémoire.
#include <iostream>
using std::cout;
using std::endl;
int main() {
auto var4 = new int;
cout << var4 << endl;
int *arr2 = new int[4];
auto arr3 = new int[4];
cout << arr2 << endl;
cout << arr3 << endl;
delete var4;
delete[] arr2;
delete[] arr3;
return EXIT_SUCCESS;
}
Founder of DelftStack.com. Jinku has worked in the robotics and automotive industries for over 8 years. He sharpened his coding skills when he needed to do the automatic testing, data collection from remote servers and report creation from the endurance test. He is from an electrical/electronics engineering background but has expanded his interest to embedded electronics, embedded programming and front-/back-end programming.
LinkedIn Facebook