La mémoire dynamique dans C++
Dans les programmes décrits dans les chapitres précédents, tous les besoins en mémoire ont été déterminés avant l’exécution du programme en définissant les variables nécessaires. Mais il peut arriver que les besoins en mémoire d’un programme ne puissent être déterminés que pendant l’exécution. Par exemple, lorsque la mémoire nécessaire dépend des entrées de l’utilisateur. Dans ces cas, les programmes doivent allouer dynamiquement de la mémoire, pour laquelle le langage C ++ intègre les opérateurs new et delete.
Opérateurs new et new []
La mémoire dynamique est allouée en utilisant l’opérateur new. new est suivi d’un spécificateur de type de données et, si une séquence de plusieurs éléments est requise, son nombre entre crochets []. Il renvoie un pointeur au début du nouveau bloc de mémoire alloué. Sa syntaxe est la suivante:
pointeur = new type
pointeur = new type [nombre_elements]
La première expression est utilisée pour allouer de la mémoire à un seul élément de type. Le second est utilisé pour allouer un bloc (un tableau) d’éléments de type type, où nombre_elements est un entier représentant le montant de ces éléments. Par exemple:
int * foo; toto = new int [5];
Dans ce cas, le système alloue dynamiquement de l’espace pour cinq éléments de type int et renvoie un pointeur sur le premier élément de la séquence, qui est affecté à foo (un pointeur). Par conséquent, foo pointe maintenant vers un bloc de mémoire valide avec un espace pour cinq éléments de type int.
Ici, foo est un pointeur et le premier élément désigné par foo est accessible avec l’expression foo [0] ou l’expression * foo (les deux sont équivalents). Le second élément est accessible avec foo [1] ou * (foo + 1), etc.
Il existe une différence substantielle entre la déclaration d’un tableau normal et l’allocation de mémoire dynamique pour un bloc de mémoire à l’aide de new. La différence la plus importante est que la taille d’un tableau régulier doit être une expression constante. Par conséquent, sa taille doit être déterminée au moment de la conception du programme, avant son exécution, alors que l’allocation dynamique de mémoire effectuée par new permet de: attribuez de la mémoire pendant l’exécution en utilisant une valeur de variable comme taille.
La mémoire dynamique demandée par notre programme est allouée par le système à partir du segment de mémoire. Cependant, la mémoire de l’ordinateur est une ressource limitée qui peut être épuisée. Par conséquent, rien ne garantit que toutes les demandes d’allocation de mémoire à l’aide de l’opérateur new seront accordées par le système.
C ++ fournit deux mécanismes standard pour vérifier si l’allocation a réussi:
L’une consiste à gérer les exceptions. En utilisant cette méthode, une exception de type bad_alloc est levée lorsque l’allocation échoue. Les exceptions sont une fonctionnalité C ++ puissante expliquée plus loin dans ces tutoriels. Mais pour l’instant, vous devez savoir que si cette exception est levée et qu’elle n’est pas gérée par un gestionnaire spécifique, l’exécution du programme est terminée.
Cette méthode d’exception est la méthode utilisée par défaut par new et est celle utilisée dans une déclaration telle que:
toto = new int [5]; // si l'allocation échoue, une exception est levée
L’autre méthode est connue sous le nom de nothrow et, lorsqu’elle est utilisée, lorsqu’une allocation de mémoire échoue, au lieu de lancer une exception bad_alloc ou de mettre fin au programme, le pointeur renvoyé par new est un pointeur nul et le programme continue son exécution. normalement.
Cette méthode peut être spécifiée en utilisant un objet spécial appelé nothrow, déclaré dans l’entête <new>, en tant qu’argument pour new:
foo = new (nothrow) int [5];
Dans ce cas, si l’allocation de ce bloc de mémoire échoue, vous pouvez le détecter en vérifiant si foo est un pointeur nul:
int * foo; foo = new (nothrow) int [5]; if (foo == nullptr) { // erreur lors de l'attribution de la mémoire. Prendre des mesures. }
Cette méthode nothrow est susceptible de produire un code moins efficace que les exceptions, car elle implique de vérifier explicitement la valeur du pointeur renvoyée après chaque attribution. Par conséquent, le mécanisme d’exception est généralement préféré, du moins pour les allocations critiques. Cependant, la plupart des exemples à venir utiliseront le mécanisme Nothrow en raison de sa simplicité.
Opérateurs delete et delete []
Dans la plupart des cas, la mémoire allouée dynamiquement n’est nécessaire que pendant des périodes spécifiques dans un programme; une fois que vous n’en avez plus besoin, vous pouvez le libérer pour que la mémoire redevienne disponible pour d’autres requêtes de mémoire dynamique. C’est l’objet de l’opérateur delete dont la syntaxe est la suivante:
delete pointeur; delete pointeur [];
La première instruction libère la mémoire d’un seul élément alloué en utilisant new, et la seconde libère la mémoire allouée pour les tableaux d’éléments en utilisant new et une taille entre parenthèses ([]).
La valeur transmise en tant qu’argument à supprimer doit être soit un pointeur sur un bloc de mémoire précédemment alloué avec new, soit un pointeur nul (dans le cas d’un pointeur nul, la suppression ne produit aucun effet).
#include <iostream> #include <new> using namespace std; int main () { int i, n; int * p; cout << "Combien de nombres voudriez-vous taper?"; cin >> i; p = new (nothrow) int [i]; si (p == nullptr) cout << "Erreur: la mémoire n'a pas pu être allouée"; else { for (n = 0; n <i; n ++) { cout << "Entrez le numéro:"; cin >> p [n]; } cout << "Vous avez entré:"; for (n = 0; n <i; n ++) cout << p [n] << ","; delete [] p; } return 0; }
Résultat de l’exécution:
Combien de nombres voudriez-vous taper? 5 Entrez le numéro: 75 Entrez le numéro: 436 Entrez le numéro: 1067 Entrez le numéro: 8 Entrez le numéro: 32 Vous avez entré: 75, 436, 1067, 8, 32,
Notez que la valeur entre crochets dans la nouvelle instruction est une valeur de variable entrée par l’utilisateur (i) et non une expression constante:
p = new (nothrow) int [i];
Il est toujours possible que l’utilisateur introduise une valeur pour i si grande que le système ne puisse pas lui allouer suffisamment de mémoire. Par exemple, lorsque j’ai essayé de donner une valeur de 1 milliard à la question « Combien de chiffres », mon système n’a pas pu allouer autant de mémoire pour le programme et j’ai reçu le message texte que nous avions préparé pour ce cas (Erreur: la mémoire n’a pas pu être allouée).
Il est considéré comme une bonne pratique que les programmes soient toujours en mesure de gérer les échecs d’allocation de mémoire, soit en vérifiant la valeur du pointeur , soit en interceptant l’exception appropriée.
Mémoire dynamique en C
C ++ intègre les opérateurs new et delete pour l’allocation de mémoire dynamique. Mais ceux-ci n’étaient pas disponibles dans la langue C; au lieu de cela, il utilisait une solution de bibliothèque, avec les fonctions malloc, calloc, realloc et free, définies dans l’en-tête <cstdlib> (connu sous le nom <stdlib.h> en C). Les fonctions sont également disponibles en C ++ et peuvent également être utilisées pour allouer et désallouer de la mémoire dynamique.
Notez cependant que les blocs de mémoire alloués par ces fonctions ne sont pas nécessairement compatibles avec ceux renvoyés par new, ils ne doivent donc pas être mélangés; chacun devrait être traité avec son propre ensemble de fonctions ou d’opérateurs.