30 votes

Pourquoi mon programme se plante-t-il lorsque j'incrémente un pointeur puis le supprime ?

J'étais en train de résoudre un exercice de programmation lorsque je me suis rendu compte que je ne comprenais pas bien les pointeurs. Quelqu'un pourrait-il m'expliquer la raison pour laquelle ce code provoque un crash en C++ ?

#include <iostream>

int main()
{
    int* someInts = new int[5];

    someInts[0] = 1; 
    someInts[1] = 1;

    std::cout << *someInts;
    someInts++; //This line causes program to crash 

    delete[] someInts;
    return 0;
}

P.S. Je suis conscient qu'il n'y a aucune raison d'utiliser "nouveau" ici, je fais juste un exemple aussi petit que possible.

80voto

Bathsheba Points 23209

C'est en fait l'instruction qui suit celle que vous marquez comme causant le plantage du programme qui cause le plantage du programme !

Vous doit passent le même pointeur à delete[] en revenant de new[] .

Sinon, le comportement du programme est indéfini .

33voto

Le problème est qu'avec le someInts++; vous passez l'adresse du deuxième élément d'un tableau à votre delete[] déclaration. Vous devez transmettre l'adresse du premier élément (original) :

int* someInts = new int[5];
int* originalInts = someInts; // points to the first element
someInts[0] = 1;
someInts[1] = 1;

std::cout << *someInts;
someInts++; // points at the second element now

delete[] originalInts;

19voto

Sinan Ünür Points 76179

Sans entrer ici dans les détails d'une implémentation spécifique, la raison intuitive derrière le crash peut être expliquée simplement en considérant ce que les delete[] est censé faire :

Détruit un tableau créé par un new[] -expression

Vous donnez delete[] un pointeur vers un tableau. Entre autres choses, il doit libérer la mémoire qu'il a allouée pour contenir le contenu de ce tableau après.

Comment l'allocateur sait-il ce qu'il doit libérer ? Il utilise le pointeur que vous lui avez donné comme clé pour rechercher la structure de données qui contient les informations comptables pour le bloc alloué. Quelque part, il existe une structure qui stocke la correspondance entre les pointeurs vers les blocs précédemment alloués et l'opération comptable associée.

Vous pouvez souhaiter que cette recherche donne lieu à une sorte de message d'erreur convivial si le pointeur que vous passez à delete [] n'était pas celui renvoyé par un new[] mais rien dans la norme ne le garantit.

Ainsi, il est possible, étant donné un pointeur qui n'avait pas été précédemment alloué par new[] , delete[] se retrouve à regarder quelque chose qui n'est pas vraiment une structure comptable cohérente. Les fils se croisent. Un accident s'ensuit.

Ou, vous pourriez souhaiter que delete[] dirait "hé, il semble que ce pointeur pointe vers un endroit à l'intérieur d'une région que j'ai allouée auparavant. Laissez-moi revenir en arrière et trouver le pointeur que j'ai renvoyé lorsque j'ai alloué cette région et l'utiliser pour rechercher les informations comptables" mais, encore une fois, il n'y a aucune exigence de ce type dans la norme :

Pour la deuxième forme (tableau), l'expression doit être une valeur de pointeur nulle ou une valeur de pointeur précédemment obtenue par une forme tableau de new-expression. Si l'expression est toute autre chose y compris si c'est un pointeur obtenu par la forme non-array de new-expression, le comportement est indéfini . [souligné par moi]

Dans ce cas, vous avez de la chance car vous avez découvert instantanément que vous aviez fait quelque chose de mal.

PS : Il s'agit d'une explication à la main

8voto

plugwash Points 795

Vous pouvez incrémenter un pointeur à l'intérieur du bloc et utiliser ce pointeur incrémenté pour accéder à différentes parties du bloc, c'est très bien.

Cependant, vous devez transmettre à Delete le pointeur que vous avez obtenu de New. Pas une version incrémentée de celui-ci, pas un pointeur qui a été alloué par un autre moyen.

Pourquoi ? Eh bien, la réponse évasive est que c'est ce que dit la norme.

La réponse pratique est que pour libérer un bloc de mémoire, le gestionnaire de mémoire a besoin d'informations sur ce bloc. Par exemple, où il commence et se termine, si les blocs adjacents sont libres (normalement, un gestionnaire de mémoire combine les blocs libres adjacents) et à quelle arène il appartient (important pour le verrouillage dans les gestionnaires de mémoire multithread).

Cette information est généralement stockée immédiatement avant la mémoire allouée. Le gestionnaire de mémoire soustrait une valeur fixe de votre pointeur et recherche une structure de métadonnées d'allocation à cet emplacement.

Si vous passez un pointeur qui ne pointe pas vers le début d'un bloc de mémoire allouée, le gestionnaire de mémoire essaie d'effectuer la soustraction et de lire son bloc de contrôle, mais ce qu'il finit par lire n'est pas un bloc de contrôle valide.

Si vous êtes chanceux, le code se bloque rapidement, si vous êtes malchanceux, vous pouvez vous retrouver avec une corruption subtile de la mémoire.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X