J'essaie de comprendre la différence entre la mémoire de la pile et la mémoire, et cette question sur SO ainsi que cette explication a fait un très bon travail expliquant les bases.
Dans la deuxième explication cependant, je suis tombé sur un exemple auquel j'ai une question spécifique, l'exemple est le suivant:
Il est expliqué que l'objet m
est alloué sur le tas , je me demande simplement s'il s'agit de l'histoire complète. D'après ma compréhension, l'objet lui-même est en effet alloué sur le tas car le mot clé new
a été utilisé pour son instanciation.
Cependant, n'est-ce pas que le pointeur vers l'objet m
est en même temps alloué sur la pile ? Sinon, comment l'accès à l'objet lui-même, qui bien sûr se trouve dans le tas . Je pense que pour être complet, cela aurait dû être mentionné dans ce tutoriel, le laisser de côté me cause un peu de confusion, alors j'espère que quelqu'un pourra clarifier cela et me dire que j'ai raison de comprendre que cet exemple devrait avoir essentiellement deux déclarations qui devraient dire:
1. un pointeur vers l'objet m
a été alloué sur la pile
2. l'objet m
lui-même (donc les données qu'il transporte, ainsi que l'accès à ses méthodes) a été alloué sur le tas
Votre compréhension peut être correcte, mais les déclarations sont fausses:
Un pointeur vers l'objet
m
a été alloué sur la pile.
m
est le pointeur. C'est sur la pile. Peut-être que vous vouliez dire pointeur vers un objet Member
.
L'objet
m
lui-même (les données qu'il transporte, ainsi que l'accès à ses méthodes) a été alloué sur le tas.
Correct serait de dire l'objet pointé par m
est créé sur le tas
En général, tout objet local de fonction/méthode et les paramètres de fonction sont créés sur la pile. Puisque m
est un objet fonction locale, il est sur la pile, mais l'objet pointé par m
est sur le tas.
"stack" et "heap" sont un jargon de programmation général. En particulier, aucun stockage n'est requis pour être géré en interne via une pile ou une structure de données de tas.
C++ possède les classes de stockage suivantes
En gros, dynamique correspond à "tas" et automatique correspond à "pile".
Passons à votre question: un pointeur peut être créé dans l'une de ces quatre classes de stockage; et les objets pointés peuvent également se trouver dans l’une de ces classes de stockage. Quelques exemples:
void func()
{
int *p = new int; // automatic pointer to dynamic object
int q; // automatic object
int *r = &q; // automatic pointer to automatic object
static int *s = p; // static pointer to dynamic object
static int *s = r; // static pointer to automatic object (bad idea)
thread_local int **t = &s; // thread pointer to static object
}
Les variables nommées déclarées sans spécificateur sont automatique si dans une fonction, ou statique sinon.
Lorsque vous déclarez une variable dans une fonction, elle va toujours sur la pile. Donc, votre variable Member* m
est créé sur la pile. Notez que par lui-même, m
n'est qu'un pointeur; cela ne montre rien. Vous pouvez l'utiliser pour pointer vers un objet sur la pile ou le tas, ou vers rien du tout.
Déclarer une variable dans une classe ou une structure est différent - ceux-ci vont là où la classe ou la structure est instanciée.
Pour créer quelque chose sur le tas, vous utilisez new
ou std::malloc
(ou leurs variantes). Dans votre exemple, vous créez un objet sur le tas à l'aide de new
et attribuez son adresse à m
. Les objets du tas doivent être libérés pour éviter les fuites de mémoire. Si alloué en utilisant new
, vous devez utiliser delete
; si alloué en utilisant std::malloc
, vous devez utiliser std::free
. La meilleure approche consiste généralement à utiliser un "pointeur intelligent", qui est un objet qui contient un pointeur et possède un destructeur qui le libère.
Oui, le pointeur est alloué sur la pile mais l'objet vers lequel pointe le pointeur est alloué sur le tas. Vous avez raison.
Cependant, n'est-ce pas que le pointeur vers l'objet m est en même temps alloué sur la pile?
Je suppose que vous vouliez dire l'objet Member
. Le pointeur est alloué sur la pile et y restera pendant toute la durée de la fonction (ou sa portée). Après cela, le code pourrait toujours fonctionner:
#include <iostream>
using namespace std;
struct Object {
int somedata;
};
Object** globalPtrToPtr; // This is into another area called
// "data segment", could be heap or stack
void function() {
Object* pointerOnTheStack = new Object;
globalPtrToPtr = &pointerOnTheStack;
cout << "*globalPtrToPtr = " << *globalPtrToPtr << endl;
} // pointerOnTheStack is NO LONGER valid after the function exits
int main() {
// This can give an access violation,
// a different value after the pointer destruction
// or even the same value as before, randomly - Undefined Behavior
cout << "*globalPtrToPtr = " << *globalPtrToPtr << endl;
return 0;
}
Le code ci-dessus stocke l'adresse d'un pointeur résidant sur la pile (et perd également de la mémoire car il ne libère pas la mémoire allouée de Object
avec delete
).
Étant donné qu'après avoir quitté la fonction, le pointeur est "détruit" (c'est-à-dire que sa mémoire peut être utilisée pour tout ce qui plaît au programme), vous ne pouvez plus y accéder en toute sécurité .
Le programme ci-dessus peut: fonctionner correctement, planter ou vous donner un résultat différent. L'accès à la mémoire libérée ou désallouée est appelé comportement indéfini .