Contacts

C Sélection de la mémoire pour une chaîne. Allocation de mémoire dynamique. Comment allouer la mémoire par le nouvel opérateur avec l'interception d'une situation critique dans laquelle la mémoire peut ne pas se démarquer? Situation exceptionnelle Bad_alloc. Exemple

Travailler avec la mémoire dynamique est souvent un goulot d'étranglement dans de nombreux algorithmes, sinon appliquant des astuces spéciales.

Dans l'article, je considérerai quelques-unes de telles techniques. Des exemples dans l'article diffèrent (par exemple, de) en ce que la surcharge des opérateurs de nouveaux et de suppression est utilisée et due à cela, les structures de syntaxe seront minimalistes et le remake du programme est simple. Également décrit les pierres sous-marines trouvées dans le processus (bien sûr, le gourou, qui lisait la norme de la croûte à peler, ne sera pas surpris).

0. Avons-nous besoin de mémoire manuelle avec la mémoire?

Tout d'abord, vérifiez comment un allocator intelligent peut accélérer le travail avec la mémoire.

Nous écrirons des tests simples pour C ++ et C # (C # est connu pour un excellent gestionnaire de mémoire, qui divise les objets pour générations, utilise différentes piscines pour des objets. differentes tailles etc.).

Noeud de classe (public: noeud * Suivant;); // ... pour (int i \u003d 0; i< 10000000; i++) { Node* v = new Node(); }

Noeud de classe (nœud public suivant;) // ... pour (int l \u003d 0; l< 10000000; l++) { var v = new Node(); }

Malgré l'ensemble du "vide sphérique" de l'exemple, la différence de temps est sortie 10 fois (62 ms contre 650 ms). De plus, C # est terminé et selon les règles de bonne tonalité en C ++, les objets dédiés doivent être supprimés, ce qui augmentera davantage la séparation (jusqu'à 2580 ms).

1. Objets de piscine

La solution évidente consiste à ramasser un gros bloc de mémoire à partir du système d'exploitation et à la diviser sur des blocs égaux de taille de taille de taille (noeud), lorsque vous allouez la mémoire, prenez un pâté de maisons de la piscine, lorsqu'elle est relâchée - retournez à la piscine. La piscine est la plus facile à organiser avec une liste unique connectée (pile).

Parce que cela vaut la peine d'interférence minimale dans le programme, tout ce qui peut être fait est d'ajouter un mélange de blockalloc à la classe de nœuds:
Nœud de classe: Blockalloc public

Tout d'abord, nous aurons besoin d'une piscine de grands blocs (pages), qui enlèvent du système d'exploitation ou de C-Runtime. Il peut être organisé sur les fonctions MALLOC et GRATUITEMENT, mais pour une plus grande efficacité (pour ignorer le niveau supplémentaire d'abstraction), nous utilisons Virtualalloc / VirtualFree. Ces fonctions allouent des blocs de mémoire, multiples 4K, ainsi que réservent l'espace d'adressage du processus par blocs, multiples 64k. Dans le même temps, spécifier les options de validation et de réserve, nous sautons un autre niveau d'abstraction, réservant l'espace d'adressage et mettez en surbrillance les pages de mémoire avec un appel.

PagePool

inline Taille_T Align (Taille_T x, Taille_T a) (retour (x-1) | (A-1) (A-1)) + 1;) // # Définir Align (x, a) (((((x) ( (a) -1)) + 1) modèle Class PagePool (Public: Void * getpage () (vide * page \u003d Virtualalloc (NULL, Pagesize, Mem_Commit | Mem_ReServe, page_readwewrite); pages.push_back (page); renvoyer la page;) ~ PagePool () (Vecteur :: itérateur i \u003d pages.begin (); I! \u003d Pages.end (); ++ i) (virtualfree (* i, 0, mem_release);)) privé: vecteur pages; );

Organisez ensuite la piscine des blocs de la taille spécifiée

Classe de blockpool

modèle. BlockPool de classe: pagePool (Public: Blockpool (): HEAD (NULL) (blocsize \u003d Align (Tailleof (T), alignement); Compte \u003d Pagesize / BlockSize;) Vide * Allocblock () (// TODO: verrouillage (! FormationNewPage (); VIDID * TMP \u003d tête; tête \u003d * (vide **) tête; renvoyer TMP;) Void freeBlock (vide * TMP) (// TODO: verrouillage (vide **) TMP \u003d Tête; Tête \u003d TMP;) privé: vidien * tête; Taille_t blocsize; Taille_t comptage; videoWewPage de format () (vide * TMP \u003d getpage (); TIM; TMP; pour (taille_t i \u003d 0; i< count-1; i++) { void* next = (char*)tmp + BlockSize; *(void**)tmp = next; tmp = next; } *(void**)tmp = NULL; } };

Commenter // TODO: Verrouiller (ceci) Les places sont marquées, qui nécessitent une synchronisation interpomatique (par exemple, utilisent une entreprise Entécritique ou Boost :: mutex).

Je vais expliquer pourquoi lorsque "Mise en forme" de la page n'utilise pas l'abstraction FreeBlock pour ajouter un bloc dans la piscine. Si quelque chose comme quelque chose comme a été écrit

Pour (taille_t i \u003d 0; i< PageSize; i += BlockSize) FreeBlock((char*)tmp+i);

Cette page sur le principe de FIFO serait marquée "au contraire":

Plusieurs blocs demandés à partir de la piscine d'affilée auraient une adresse décroissante. Et le processeur n'aime pas revenir en arrière, il se casse avec la préfetch ( UPD.: Pas pertinent pour processeurs modernes). Si vous faites un balisage dans le cycle
Pour (taille_t i \u003d pagesize- (blocsize- (pagesizize% bloqusize)); i! \u003d 0; i - \u003d blocksize) freeblock ...
Le cycle de balisage irait aux adresses à l'envers.

Maintenant que les préparatifs sont faits, vous pouvez décrire la classe de grade.
Modèle. Classe Blockalloc (Public: Statique Void * Opérateur Nouveau (Taille_T S) (si (S! \u003d Tailleof (t)) (retour :: Opérateur Nouveau (s);) Retour piscine.Allocblock ();) Opérateur de vide statique Supprimer (annuler * M, taille_t s) (si (s! \u003d Tailleof (t)) (:: Supprimer l'opérateur (M);) Autres si (m! \u003d Null) (piscine .... Void statique * Opérateur neuf (taille_t, vide * M) (retour M;) // ... et l'avertissement sur l'emplacement manquant Supprimer ... Opérateur de vide statique Supprimer (vide *, vide *) () Privé: Statique Blockpool Piscine; ); Modèle. Blockpool Blockalloc. :: Piscine;

Je vais expliquer pourquoi vous avez besoin de chèques si (s! \u003d Tailleof (t)))
Quand travaillent-ils? Ensuite, lorsque la classe est créée / supprimée de la base T.
Les héritiers utiliseront le nouveau / Supprimer d'habitude, mais vous pouvez également avoir besoin de mélanger Blockalloc. Ainsi, nous sommes déterminés facilement et en toute sécurité quelles classes devraient utiliser les piscines, sans craindre de casser quelque chose dans le programme. Plusieurs héritages fonctionnent également très bien avec ce mélange.

Prêt. Inheriter le nœud de Blockalloc et réédirez un test.
Le temps de test est maintenant de 120 ms. 5 fois plus vite. Mais dans C #, l'allocator est toujours mieux. Probablement, il n'y a pas qu'une liste connectée. (Si immédiatement après nouveau, appelez immédiatement Supprimer et ne dépense donc pas beaucoup de mémoire, connaître les données dans le cache, nous obtenons 62 ms. Etrange. Exactement comme un CLR U.net, comme s'il renvoie immédiatement les variables locales libérées immédiatement à la piscine concernée, sans attendre GC)

2. Conteneur et sa teneur en pétrole

Est-ce que cela rencontre souvent des cours qui stockent beaucoup de filiales différentes, de sorte que la durée de vie du dernier dépassement de la durée de vie du parent?

Par exemple, il peut s'agir d'une classe XMLDocument remplie de classes de nœud et d'attributs, ainsi que des chaînes C (Char *) extraites du texte à l'intérieur du nœud. Ou liste de fichiers et répertoires dans gestionnaire de fichiersTéléchargé une fois lorsqu'il s'agit de relire le répertoire et ne change plus.

Comme l'a été montré dans l'introduction, Supprimer est plus chère que neuf. L'idée de la deuxième partie de l'article consiste à attribuer la mémoire pour les objets enfants dans un bloc important associé à l'objet parent. Lorsque vous retirez l'objet parent, les filiales seront, comme d'habitude, sont causées par destructeurs, mais la mémoire ne sera pas retournée - il est libre d'obtenir un grand bloc.

Créez une classe Pointerbumpallocator, qui est capable de mordre à partir d'un grand morceau de morceaux de tailles différentes et de mettre en évidence un nouveau bloc de grand bloc lorsque l'ancien sera épuisé.

Classe de pointerbumpallocator

modèle. PointerBumplollocator (Public: Pointerbumpallocator (): Gratuit (0) (0) () vide * Allocblock (bloc_t block) (// TODO: verrouillage (this) bloc \u003d alignement (bloc, alignement); Si (Bloc\u003e Gratuit) (GRATUIT \u003d Aligner (bloc, pagesize); tête \u003d getpage (gratuit);) vide * TMP \u003d tête; tête \u003d (char *) tête + bloc; free - \u003d block; renvoyer TMP;) ~ Pointerbumpallocator () (Vecteur :: itérateur i \u003d pages.begin (); I! \u003d Pages.end (); ++ i) (virtualfree (* i, 0, mem_release);)) privé: vide * getpage (Taille_t taille) (vide * page \u003d virtualalloc (, taille, mem_commit | mem_rerve, page_readwewrite); pages.push_back (page) ; Page de retour;) vecteur Pages; Vider * tête; Taille_t gratuit; ); Typedef pointerbumpallocator<> Defaultacator;

Enfin, nous décrivons le mélange ChildObject avec la nouvelle et la suppression surchargée, adressée à l'alloctor spécifié:

Modèle. Structure ChildObject (retour Allocator.AllockBlock (s);) Vidéo statique * Opérateur Nouveau (retournement Allocator-\u003e Allocblock (s);) Opérateur vide Statique Supprimer (vide *, Taille_T) () // * Opérateur de vide statique Supprimer (annuler *, A *) () Opérateur de vide statique Supprimer (vide *, A &) () privé: statique Void * Opérateur Nouveau (taille_t s););

Dans ce cas, en plus d'ajouter une impureté dans la classe enfant, il sera également nécessaire de corriger tous les appels au nouveau (ou d'utiliser le modèle d'usine). La nouvelle syntaxe des opérateurs sera la suivante:

Nouveau (... Paramètres pour l'opérateur ...) ChildObject (... Paramètres de designer ...)

Pour plus de commodité, j'ai défini deux nouveaux opérateurs recevant A & A..
Si l'allocator est ajouté à la classe mère en tant que membre, plus pratique pour la première option:
Nœud \u003d nouveau (alocator) xmlnode (nodename);
Si l'allocator est ajouté comme ancêtre (mélange), il est plus pratique pour la seconde:
Nœud \u003d nouveau (this) xmlnode (NODENAME);

Une syntaxe spéciale n'est pas fournie pour l'appel Supprimer, le compilateur provoquera une suppression standard (marquée * 1), quelle que soit la part des nouvelles états utilisés pour créer un objet. C'est à dire, syntaxe Supprimer. Ordinaire:
Supprimer le nœud;

S'il y a une exception dans le concepteur ChildObject (ou son héritier), une suppression est appelée avec une signature correspondant à la signature du nouvel opérateur utilisé lors de la création de cet objet (le premier paramètre Taille_t sera remplacé par Void *).

Le placement du nouvel opérateur dans la section privée protège contre le nouveau paramètre Allest.

Je vais donner un exemple complet d'utilisation de la paire Allocator-ChildObject:

Exemple

cLASSE XMLDOCUMUMUMENT: PUBLICALALLALLOCATOR (PUBLIC: ~ XMLDOCUMUMUMENT () (pour (vecteur :: itérateur i \u003d nœuds.begin (); I! \u003d Nœuds.end (); ++ i) (Supprimer (* i);)) vide addnode (Char * Contenu, Char * Nom) (Char * C \u003d (Char *) Allocblock (Strlen (Contenu) +1); Strcpy (C, Contenu); Char * n \u003d (Char *) Allocblock (Strlen (nom (nom) +1); Strcpy (n, contenu); noeuds.push_back (nouveau xmlnode (c, n));) Classe XMLNode: Public ChilkObject (Public: xmlnode (char * _content, char * _name): contenu (_Content), nom (nommé) () privé: char * contenu; Char * Nom;); Privé: Vector nœuds; );

Conclusion. L'article a été écrit il y a 1,5 ans pour le bac à sable, mais hélas n'aimait pas le modérateur.

    stocke des variables et des constantes globales;

    la taille est déterminée lors de la compilation.

    Pile (pile)

    stocke les variables locales, les arguments de fonctionnalités et les valeurs de calcul intermédiaire;

    la taille est déterminée lorsque le programme est démarré (4 Mo est généralement attribué).

    Tas (tas)

    mémoire distribuée dynamiquement;

    Le système d'exploitation met en évidence la mémoire pour les pièces (selon les besoins).

La mémoire distribuée dynamique doit être utilisée si nous sommes à l'avance (au moment de la rédaction d'un programme) Nous ne savons pas combien de mémoire nous aurons besoin (par exemple, la taille de la matrice dépend de ce que l'utilisateur entre dans le programme) et lorsque vous travaillez avec de gros volumes de données.

La mémoire dynamique, également appelée "pile", est allouée explicitement à la demande du programme des ressources système opérateur et contrôlé par le pointeur. Il n'est pas initialisé automatiquement et devrait être clairement libéré. Contrairement à la mémoire statique et automatique, la mémoire dynamique n'est pratiquement pas limitée (limitée uniquement par la taille. mémoire vive) et peut changer pendant le programme

Travailler avec la mémoire dynamique avec

Pour travailler avec la mémoire dynamique dans la langue, les fonctions suivantes sont utilisées: malloc, calloc, libre, realloc. Considérez-les plus en détail.

    Sélection (capture de mémoire): Void * MALLOC (Taille_T Taille);

En tant que paramètre d'entrée, la fonction prend la taille de la mémoire que vous souhaitez mettre en surbrillance. La valeur renvoyée est le pointeur sur le site de la mémoire alloué dans une pile. Si le système d'exploitation n'était pas capable d'allouer la mémoire (par exemple, la mémoire n'était pas suffisante), puis MALLOC retourne 0.

    Après la fin du travail avec la mémoire dynamique allouée, vous devez la libérer. Pour cette fin, la fonction libre est utilisée, qui renvoie la mémoire pour le système d'exploitation contrôlé: vide gratuit (VOID * PTR);

Si la mémoire est libérée avant la fin du programme, elle est automatiquement exemptée à la fin du programme. Néanmoins, la libération explicite de la mémoire inutile est un signe d'un bon style de programmation.

Exemple: // Répartition de la mémoire sous 1 000 éléments de type int

int * p \u003d (int *) malloc (1000 * taille de (int));

si (p \u003d\u003d null) cout<< "\n память не выделена";

libre (p); // remboursement de la mémoire dans un tas

2. Allocation (capture de mémoire): vide * calloc (taille_t nmemb, taille_t taille);

La fonction fonctionne de la même manière à MALLOC, mais se caractérise par la syntaxe (au lieu de la taille de la mémoire allouée, vous devez spécifier le nombre d'éléments et de taille d'un élément) et le fait que la mémoire sélectionnée est réinitialisée. Par exemple, après avoir effectué l'intégration int * p \u003d (int *) calloc (1000, Tailleof (int)) P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, Perfera au début d'une matrice de type INT de 1000 éléments initialisés avec des zéros.

3. Modification de la taille de la mémoire: Void * Realloc (vide * PTR, Taille_T Taille);

La fonction change la taille de la mémoire allouée (qui indique pTR, Reçu de l'appel malloc, calloc ou realloc). Si la taille spécifiée dans le paramètre taille plus que celui qui a été mis en surbrillance sous le pointeur pTR, On vérifie s'il est possible de mettre en évidence les cellules manquantes de la mémoire d'affilée avec déjà dédiée. S'il n'y a pas assez d'espace, une nouvelle section de mémoire est distinguée taille et données indicatrices pTR. Copier en haut d'un nouveau site.

Dans le processus d'exécution du programme, le site de mémoire dynamique est disponible partout où le pointeur s'adresse à cette section est disponible. Ainsi, les trois options suivantes sont possibles avec une mémoire dynamique allouée dans une certaine unité (par exemple, dans la fonction de déroulement).

    Le pointeur (sur le site de mémoire dynamique) est défini comme un objet de mémoire automatique local. Dans ce cas, la mémoire sélectionnée n'est pas disponible lorsque le bloc d'emplacement émet le bloc d'emplacement et il est nécessaire de le relâcher avant de quitter le bloc.

(int * p \u003d (int *) calloc (n, taille de (int))

libre (p); // libération DIN. Mémoire

    Le pointeur est défini comme une mémoire statique locale. La mémoire dynamique allouée une fois dans le bloc est disponible via un pointeur à chaque retenue dans le bloc. La mémoire doit être libérée uniquement à la fin de son utilisation.

(Statique int * p \u003d (int *) calloc (n, taille de (int));

p \u003d (int *) calloc (n, taille de (int));

f (50); // allocation de doyenne. Mémoire avec version ultérieure

f1 (100); // allocation de doyenne. Mémoire (premier appel)

f1 (100); // Travailler avec Dean. Mémoire

f1 (0); // libération DIN. Mémoire

    Le pointeur est un objet global par rapport au bloc. La mémoire dynamique est disponible dans tous les blocs où le pointeur est "visible". La mémoire doit être libérée uniquement à la fin de son utilisation.

int * pg; // pointeur de travail pour Dean. Mémoire (variable globale)

vide init (taille int)

pour (i \u003d 0; i< size; i++) //цикл ввода чисел

(Printf ("x [% d] \u003d", i);

scanf ("% d", & pg [i]);

somme int somme (taille int)

pour (i \u003d 0; i< size; i++) //цикл суммирования

// allocation de mémoire

pg \u003d (int *) calloc (n, taille de (int));

// Travailler avec Dean Pam

printf (\\ ns \u003d% d \\ n ", somme (n));

libre (pg); pg \u003d null; // libération de la mémoire

Travailler avec la mémoire dynamique en C ++

En C ++, il existe un mécanisme d'attribution et de libération de la mémoire - ce sont des fonctions. nouveau et effacer.Exemple d'utilisation nouveau: int * p \u003d nouveau int; // Sélection de la mémoire sous 1000 el-ordre I.e. Lorsque vous utilisez la fonction nouveau Pas besoin de donner un pointeur et de ne pas avoir besoin d'utiliser taille de (). Libération allouée par nouveau La mémoire est effectuée en suivant l'appel suivant: Supprimer P; Si vous souhaitez sélectionner la mémoire pour un élément, vous pouvez utiliser INT * q \u003d nouveau INT; ou int * q \u003d nouveau int (10); // sélectionné INT est initialisé par la valeur de 10 dans ce cas, l'élimination ressemblera à ceci: supprimer q;

délai de mise en œuvre programmes. Sous Variables locales, le programme prend la mémoire de l'espace de pile. Cependant, les variables locales nécessitent une détermination préliminaire de la quantité de mémoire allouée à chaque situation. Bien que C ++ implémente efficacement ces variables, elles nécessitent un programmeur de savoir à l'avance quelle quantité de mémoire est nécessaire à chaque situation.

La deuxième manière que c ++ peut stocker des informations consiste à utiliser le système de distribution dynamique. Dans cette méthode, la mémoire est distribuée pour plus d'informations de la zone de mémoire libre au besoin. La zone de mémoire libre est entre le code de programme avec sa zone de mémoire constante et sa pile (Fig. 24.1). L'hébergement dynamique est pratique lorsqu'il est inconnu, combien d'éléments de données seront traités.


Figure. 24.1.

Comme le programme utilise la zone de pile augmente, c'est-à-dire que le programme lui-même détermine le volume de la mémoire de pile. Par exemple, un programme de grand nombre fonctions récursives prendra plus de mémoire de pile qu'un programme qui n'a pas fonctions récursivesLes variables locales et les adresses de retour sont stockées dans des piles. Mémoire pour le programme lui-même et variables globales se démarquer sur tout délai de mise en œuvre Les programmes sont constants pour un environnement spécifique.

La mémoire allouée dans le processus d'exécution du programme est appelée dynamique. Après la sélection dynamique Il est enregistré dans sa libération explicite, qui ne peut être effectuée qu'avec une fonction spéciale ou une fonction de bibliothèque.

Si la mémoire dynamique n'est pas publiée avant la fin du programme, elle est exempte automatiquement à la fin du programme. Néanmoins, la libération explicite de la mémoire inutile est un signe d'un bon style de programmation.

Dans le processus d'exécution du programme, le site de mémoire dynamique est disponible partout où le pointeur s'adresse à cette section est disponible. Ainsi, les éléments suivants sont possibles. trois options de travail avec mémoire dynamiquesécrété dans une certaine unité (par exemple, dans le corps de la part de la part de la part).

  • Le pointeur (sur le site de mémoire dynamique) est défini comme un objet de mémoire automatique local. Dans ce cas, la mémoire sélectionnée n'est pas disponible lorsque le bloc d'emplacement émet le bloc d'emplacement et il est nécessaire de le relâcher avant de quitter le bloc.
  • Le pointeur est défini comme une mémoire statique locale. La mémoire dynamique allouée une fois dans le bloc est disponible via un pointeur à chaque retenue dans le bloc. La mémoire doit être libérée uniquement à la fin de son utilisation.
  • Le pointeur est un objet global par rapport au bloc. La mémoire dynamique est disponible dans tous les blocs où le pointeur est "visible". La mémoire doit être libérée uniquement à la fin de son utilisation.

Toutes les variables déclarées dans le programme sont placées dans une zone de mémoire continue appelée segment de données. De telles variables ne changent pas de taille pendant l'exécution du programme et sont appelées statique. La taille du segment de données peut ne pas suffire à placer de grandes quantités d'informations. La sortie de cette situation consiste à utiliser la mémoire dynamique. Mémoire dynamique - Il s'agit de la mémoire allouée par le programme pour son travail moins le segment de données, la pile dans laquelle se trouvent des variables locales des sous-routines et du programme.

Les pointeurs utilisent des pointeurs pour travailler avec une mémoire dynamique. Avec leur aide, l'accès aux sites de mémoire dynamique appelés variables dynamiques. Pour le stockage variables dynamiques Une zone de mémoire spéciale est mise en surbrillance, appelée "bugs".

Variables dynamiques Créé avec des fonctions et des opérations spéciales. Ils existent soit jusqu'à la fin du programme, soit jusqu'à ce que la mémoire soit allouée à elles soit libérée à l'aide de fonctions ou d'opérations spéciales. C'est-à-dire une vie variables dynamiques - du point de création à la fin du programme ou explicite libérer la mémoire.

C ++ utilise deux façons de travailler avec la mémoire dynamique:

  1. utilisation d'opérations nouvelles et supprimées;
  2. l'utilisation de la famille de fonctions malcos (calloc) (héritée de c).

Travailler avec la mémoire dynamique utilisant des opérations nouvelles et supprimées

Dans la langue de programmation C ++ pour distribution de mémoire dynamique Il y a des opérations nouvelles et supprimées. Ces opérations sont utilisées pour mettre en évidence et libérer des blocs de mémoire. La zone de mémoire dans laquelle ces blocs sont placés, appelés mémoire libre.

La nouvelle opération vous permet de mettre en évidence et de créer une parcelle gratuite accessible dans la mémoire principale, dont les dimensions correspondent au type de données définies par le nom de type.

Syntaxe:

nouveau nameType;

nouveau nameType [initialiseur];

La zone en surbrillance est entrée par la valeur déterminée par initialiseurqui n'est pas un élément obligatoire. Dans le cas d'une exécution réussie, le nouveau renvoie l'adresse du début de la zone de mémoire sélectionnée. Si la section des dimensions souhaitées ne peut pas être sélectionnée (pas de mémoire), la nouvelle opération renvoie la valeur d'adresse zéro (null).

Fonctionnement de l'application Syntaxe:

Pointeur \u003d nouveau nameType [initialiseur];

La nouvelle opération de flottaison met en évidence la zone de mémoire de 4 octets. La nouvelle opération INT (15) met en évidence la zone de mémoire de 4 octets et initialise ce site avec une valeur totale 15. La syntaxe d'utilisation du réseau et la suppression impliquent l'utilisation des pointeurs. Auparavant, chaque pointeur doit être annoncé:

type * Indicateur de noms;

Par example:

flotteur * pi; // Annonce de la PI PI \u003d nouvelle variable de flotteur; // Sélection de la mémoire pour la variable PI * PI \u003d 2,25; // Affectation de la valeur

En tant que tels que le type, tels que des types standard peuvent être utilisés. int, long, flotteur, double, char.

Le nouvel opérateur est le plus souvent utilisé pour héberger les types spécifiques au type dans la mémoire des données, par exemple des structures:

nœud de structure (char * nom; valeur int; noeud * suivant); Nœud * pnode; // annoncé pnode \u003d nouveau pointeur de nœud; // pnode-\u003e nom \u003d "ATA" est attribué; // attribue pnode-\u003e valeur \u003d 1 valeurs; Pnode-\u003e suivant \u003d null;

En tant que type de nom de nouveau fonctionnement, une matrice peut être utilisée:

nouveau Timassiva

Lorsque vous allouez une mémoire dynamique pour la matrice, ses tailles doivent être entièrement définies. Par example:

pTR \u003d neuf int; // 10 éléments de type int ou 40 octets ptr \u003d nouveau int; // incorrectement, car Taille non définie

Une telle opération vous permet de mettre en évidence la zone dans la mémoire dynamique pour placer un tableau du type correspondant, mais ne le permet pas d'initialiser. À la suite de l'exécution, la nouvelle opération retournera le pointeur, dont la valeur est l'adresse du premier élément du tableau. Par example:

int * n \u003d neuf int;

La nouvelle opération effectue un type de mémoire dynamique suffisamment suffisant pour placer la valeur de l'INT et enregistre l'adresse du début de cette zone dans la variable N. La mémoire sous la variable n (taille suffisante pour placer un pointeur) est mise en surbrillance au stade de la compilation.

Très souvent, il existe des tâches de traitement des tableaux de données, dont la dimension est à l'avance inconnue. Dans ce cas, il est possible d'utiliser l'une des deux approches:

  • sélection de la mémoire pour une matrice statique contenant le nombre maximal possible d'éléments, mais dans ce cas, la mémoire n'est pas consommée rationnelle;
  • allocation de mémoire dynamique pour stocker une matrice de données.

Pour utiliser des fonctions d'allocation de mémoire dynamique, il est nécessaire de décrire le pointeur, qui est l'adresse initiale du stockage des éléments de matrice.

int * p; // pointeur sur tapez int

L'adresse initiale de la matrice statique est déterminée par le compilateur au moment de son annonce et ne peut être modifiée.

Pour une matrice dynamique, l'adresse initiale est attribuée au pointeur déclaré à la matrice pendant l'exécution du programme.

Fonctions d'allocation de mémoire dynamique standard

Les fonctions de la mémoire dynamique se trouvent dans la RAM, une partie continue de la longueur requise et renvoie l'adresse initiale de cette zone.

Fonctions de distribution de mémoire dynamique:

vide * malloc (taille massivivable);
vide * calloc (numérotés, format ollementavabytes);

Pour utiliser les fonctions de distribution de la mémoire dynamique, vous devez connecter la bibliothèque. :

#Inclure.

Étant donné que les deux fonctionnalités présentées comme valeur renvoyée ont un pointeur à un type vide vide, une attribution explicite du type de valeur de retour est requise.

Pour déterminer la taille d'un tableau dans des octets utilisés comme argument de fonction MALLOC (), le nombre d'éléments est nécessaire pour se multiplier par un élément. Étant donné que les éléments de la matrice peuvent être à la fois ces données de type et les types composites (par exemple, les structures), pour déterminer avec précision la taille de l'élément généralement recommandé l'utilisation de la fonction

intégralité de la taille de (type);


qui définit la quantité d'octet occupé par l'élément du type spécifié.

Mémoire, surlignée de manière dynamique à l'aide de fonctions calloc (), MALLOC (), peut être publiée en utilisant la fonction

libre (pointeur);

La «règle de ton commun» dans la programmation est la libération de la mémoire allouée de manière dynamique en l'absence de son utilisation supplémentaire. Toutefois, si la mémoire dédiée de manière dynamique n'est pas exemptée, elle sera publiée à la fin de l'exécution du programme.

Allocation de mémoire dynamique pour les tableaux unidimensionnels

La forme de circulation aux éléments de la matrice à l'aide des pointeurs est la forme suivante:

int A, * p; // décrire un tableau statique et un pointeur
int b;
p \u003d a; // attribue l'adresse initiale de la matrice
... // entrant dans les éléments du tableau
b \u003d * p; // b \u003d a;
b \u003d * (p + i) // b \u003d a [i];

Exemple sur C: Organisation d'une matrice unidimensionnelle dynamique et d'entrée de ses éléments.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27


#Inclure.
#Inclure.
#Inclure.
iNT MAIN ()
{
int * a; // pointeur sur un tableau
int i, n;
Système ("CHCP 1251");
Système ("CLS");
Printf ( "Entrez la taille de la matrice:");
Scanf ("% d", & n);
// allocation de mémoire
a \u003d (int *) malloc (n * tailleof (int));
// entrant dans les éléments du tableau
pour (i \u003d 0; i {
Printf ("a [% d] \u003d", i);
Scanf ("% d", et a [i]);
}
// Conclusion des éléments du tableau
pour (i \u003d 0; i Printf ("% d", [i]);
Libre (a);
getchar (); getchar ();
retour 0;
}


Le résultat du programme:

Allocation de mémoire dynamique pour les tableaux bidimensionnels

Laissez-les avoir lieu dans la mémoire dynamique une matrice contenant N cordes et m colonnes. La matrice à deux dimensions sera située dans la RAM sous la forme d'un ruban constitué d'éléments de cordes. Dans ce cas, l'indice de tout élément de la matrice à deux dimensions peut être obtenu par la formule

index \u003d i * m + j;

où je suis le numéro de ligne actuel; J est le numéro de la colonne actuelle.

Considérons la matrice 3x4 (voir fig.)

L'index de l'élément dédié sera déterminé comme

index \u003d 1 * 4 + 2 \u003d 6

La quantité de mémoire requise pour accueillir un tableau bidimensionnel est déterminée comme

n · m · (taille d'élément)

Cependant, depuis avec cette déclaration, le compilateur n'indique clairement pas clairement le nombre d'éléments de la ligne et de la colonne de la matrice bidimensionnelle, l'appel traditionnel à l'élément en spécifiant l'index de la chaîne et l'index de la colonne est incorrect:

un [i] [j] - incorrect.

Le bon appel à l'élément utilisant le pointeur ressemblera à

* (P + i * m + j),

  • p est un pointeur de tableau
  • m - nombre de colonnes,
  • i - Index des lignes,
  • j est l'index de la colonne.

Exemple sur Si

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

#Define _crt_secure_no_warnings.
#Inclure.
#Inclure.
#Inclure.
iNT MAIN ()
{
int * a; // pointeur sur un tableau
int i, j, n, m;
Système ("CHCP 1251");
Système ("CLS");
Printf ( "Entrez le nombre de lignes:");
Scanf ("% d", & n);
printf ();
Scanf ("% d", & m);
// allocation de mémoire
a \u003d (int *) malloc (n * m * tailleof (int));
// entrant dans les éléments du tableau
pour (i \u003d 0; i // cycle de rangée
{
pour (j \u003d 0; j // cycle sur les colonnes
{
Scanf ("% d", (a + i * m + j));
}
}
// Conclusion des éléments du tableau
pour (i \u003d 0; i // cycle de rangée
{
pour (j \u003d 0; j // cycle sur les colonnes
{
Printf ("% 5d", * (A + i * m + j));
}
Printf ("\\ n");
}
Libre (a);
getchar (); getchar ();
retour 0;
}

Résultat de l'exécution

Une autre méthode de mémoire dynamique est également possible pour une matrice bidimensionnelle - à l'aide d'une gamme de pointeurs. Pour cela, vous avez besoin de:

  • sélectionnez l'unité RAM sous le tableau des pointeurs;
  • sélectionnez les blocs de la RAM pour les tableaux unidimensionnels, qui sont les rangées de la matrice souhaitée;
  • enregistrez des adresses de lignes vers le tableau des pointeurs.

Une telle méthode d'allocation de mémoire peut être représente graphiquement comme suit.


Avec cette méthode, la taille du compilateur est clairement indiquée par le nombre de lignes et le nombre de colonnes dans la matrice.
Exemple sur Si

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

#Define _crt_secure_no_warnings.
#Inclure.
#Inclure.
#Inclure.
iNT MAIN ()
{
int ** a; // pointeur sur le pointeur sur la ligne d'éléments
int i, j, n, m;
Système ("CHCP 1251");
Système ("CLS");
Printf ( "Entrez le nombre de lignes:");
Scanf ("% d", & n);
Printf ( "Entrez le nombre de colonnes:");
Scanf ("% d", & m);
// Sélection de la mémoire pour les lignes
// entrant dans les éléments du tableau
pour (i \u003d 0; i // cycle de rangée
{
// Sélection de la mémoire pour les chaînes de stockage
un [I] \u003d (int *) malloc (m * tailleof (int));
pour (j \u003d 0; j // cycle sur les colonnes
{
Printf ("a [% d] [% d] \u003d", je, j);
Scanf ("% d", et a [i] [j]);
}
}
// Conclusion des éléments du tableau
pour (i \u003d 0; i< n; i++) // cycle de rangée
{
pour (j \u003d 0; j< m; j++) // cycle sur les colonnes
{
Printf ("% 5d", un [i] [j]); // 5 connaissance sous l'élément du tableau
}
Printf ("\\ n");
}
// Nettoyage de la mémoire
pour (i \u003d 0; i< n; i++) // cycle de rangée
Libre (a [i]); // libération de la mémoire pour la ligne
Libre (a);
getchar (); getchar ();
retour 0;
}

Le résultat du programme est similaire à celui précédent.

À l'aide d'une allocation de mémoire dynamique sous les pointeurs de rangée, vous pouvez placer des tableaux gratuits. Un tableau bidimensionnel (matrice) est libre, dont la taille de la chaîne peut être différente. L'avantage d'utiliser un tableau GRATUIT est qu'il n'est pas nécessaire de supprimer la mémoire de l'ordinateur avec une réserve pour placer la chaîne de la longueur maximale possible. En fait, le tableau GRATUIT est un éventail unidimensionnel de pointeurs à des tableaux de données unidimensionnels.

Pour accueillir la matrice de la RAM avec des lignes de différentes longueurs, vous devez entrer un tableau supplémentaire M, dans lequel la taille des chaînes sera stockée.

Exemple sur Si

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

#Define _crt_secure_no_warnings.
#Inclure.
#Inclure.
iNT MAIN ()
{
int ** a;
int i, j, n, * m;
Système ("CHCP 1251");
Système ("CLS");
Printf ( "Entrez le nombre de lignes:");
Scanf ("% d", & n);
a \u003d (int **) malloc (n * tailleof (int *));
m \u003d (int *) malloc (n * tailleof (int)); // matrice du nombre d'éléments dans les lignes de tableau A
// entrant dans les éléments du tableau
pour (i \u003d 0; i {
Printf ( "Entrez le nombre de colonnes% d:", JE);
Scanf ("% d", & m [i]);
a [I] \u003d (int *) malloc (m [i] * Tailleof (int));
pour (j \u003d 0; j Printf ("a [% d] [% d] \u003d", je, j);
Scanf ("% d", et a [i] [j]);
}
}
// Conclusion des éléments du tableau
pour (i \u003d 0; i {
pour (j \u003d 0; j {
Printf ("% 3d", un [i] [j]);
}
Printf ("\\ n");
}
// libération de la mémoire
pour (i \u003d 0; i< n; i++)
{
Libre (a [i]);
}
Libre (a);
Libre (m);
getchar (); getchar ();
retour 0;
}


Résultat de l'exécution

Redistribution de la mémoire

Si la taille de la mémoire de la mémoire ne peut pas être spécifiée à l'avance, par exemple, lors de la saisie d'une séquence de valeurs à une commande spécifique, d'augmenter la taille de la matrice, lorsque vous entrez la valeur suivante, vous devez effectuer une exécution. les étapes suivantes:

  • Sélectionnez l'unité de dimension de N + 1 (1 de plus que la taille actuelle de la matrice)
  • Copier toutes les valeurs stockées dans un tableau dans une zone de mémoire nouvellement dédiée.
  • Mémoire libre allouée plus tôt pour le stockage du tableau
  • Déplacez le pointeur pour lancer le tableau au début de la zone de mémoire nouvellement sélectionnée.
  • Ajouter une matrice dernière valeur introduite

Toutes les étapes ci-dessus (sauf la dernière) effectue la fonction

vide * realloc (vide * PTR, taille_t taille);

  • le PTR est un pointeur sur un bloc de mémoire préalablement allouée avec des fonctions MALLOC (), CALLOC () ou pour passer à un nouvel endroit. Si ce paramètre est NULL, la nouvelle unité est allouée et la fonction renvoie un pointeur à celui-ci.
  • la taille est une nouvelle taille, en octets du bloc de mémoire alloué. Si Taille \u003d 0, la mémoire précédemment dédiée est publiée et la fonction renvoie un pointeur zéro, le PTR est installé dans NULL.

La taille du bloc de mémoire auquel le paramètre PTR fait référence à des modifications apportées à la taille des octets. Le bloc de mémoire peut diminuer ou augmenter de taille. Le contenu du bloc de mémoire est préservé même si la nouvelle unité a une taille plus petite que l'ancienne. Mais ces données qui vont au-delà du nouveau bloc sont supprimées. Si le nouveau bloc de mémoire est supérieur à l'ancien, le contenu de la mémoire nouvellement attribuée sera incertain.
si (i\u003e 2) i - \u003d 2;
Printf ("\\ n");
a \u003d (int *) realloc (a, i * tailleof (int)); // réduisant la taille de la matrice pour 2
pour (int j \u003d 0; j< i; j++)
Printf ("% d", un [j]);
getchar (); getchar ();
retour 0;
}

Le programme peut stocker des informations dans la mémoire principale de l'ordinateur de deux manières principales. Les premiers utilisent des variables globales et locales, y compris des tableaux, des structures et des cours. Dans le cas des variables locales globales et statiques, l'emplacement de stockage est fixé pour toute l'heure d'exécution du programme. Dans le cas des variables locales, la mémoire est mise en surbrillance dans la pile. Bien que dans Borland C ++, travailler avec ces variables soit mis en œuvre très efficacement, leur utilisation nécessite un programmeur de savoir à l'avance la taille de la mémoire qui sera requise lors de l'exécution du programme.

La deuxième méthode d'enregistrement d'informations est l'utilisation d'un système de mémoire dynamique de la mémoire Borland C ++. Dans cette méthode, la mémoire pour stocker des informations est attribuée à partir de la zone de mémoire libre au besoin et retourne en arrière, c'est-à-dire Il est libéré lorsque la nécessité de la disparition. La zone de mémoire libre se situe entre la zone de mémoire où se trouve le programme et la pile. Cette zone s'appelle un bouquet (tas) et est utilisé pour les demandes d'allocation de mémoire dynamique.

L'avantage de l'utilisation de la mémoire dynamique est que la même mémoire peut être utilisée pour stocker diverses informations dans le processus d'exécution du programme. Étant donné que la mémoire est allouée dans un but spécifique et est relâchée lorsque son utilisation est terminée, vous pouvez utiliser la même mémoire à un autre moment donné à d'autres fins dans une autre partie du programme. Un autre avantage de la répartition de la mémoire dynamique est la possibilité de créer des listes associées, des arbres binaires et d'autres structures de données dynamiques.

Le noyau de l'allocation de mémoire dynamique de la mémoire de la langue avec est les fonctions Funoc () et GRATUITES () parties de la bibliothèque standard. Chaque fois que la fonction MALLOC (), une mémoire est reçue pour l'attribution de mémoire, une partie de la mémoire libre disponible est distinguée. Chaque fois que cette mémoire est libérée à l'aide de la fonction GRATUITE (), cette mémoire renvoie le système.

C ++ Langue définit deux opérateurs d'allocation de mémoire dynamique - NOUVEAU et Supprimer.

La norme ANSI C ne définit que quatre fonctions d'allocation de mémoire dynamique: calloc (), malloc (), gratuit () et realloc (). Cependant, Borland C ++ contient plusieurs autres fonctions d'allocation de mémoire dynamique. Lors de la compilation de code pour un modèle de mémoire moderne de 32 bits, la mémoire est plate et utilisée généralement quatre fonctions d'allocation de mémoire standard.

La norme ANSI C définit que les informations d'en-tête requises pour la répartition de la mémoire dynamique sont contenues dans le fichier STDLIB.H. Cependant, Borland C ++ vous permet d'utiliser stdlib.h ou alloc.h. Ici, nous utilisons le fichier d'en-tête STDLIB.H, car il fournit une portabilité. Certaines autres fonctions d'allocation de mémoire dynamique nécessitent Alloc.h des fichiers d'en-tête, Malloc.h ou Dos.H. Il est nécessaire de porter une attention particulière à laquelle le fichier d'en-tête est nécessaire pour utiliser chaque fonction.



Avez-vous aimé l'article? Partagez-le