53 votes

Le traducteur a commis une erreur en expliquant la pile et le tas en C++ ou est-ce que je lis mal quelque chose?

Voici le code :

int main()
{
    using namespace std; 
    int nights = 1001; 
    int * pt = new int; // allouer de l'espace pour un int
    *pt = 1001;         // y stocker une valeur

    cout << "valeur de nights = ";
    cout << nights << ": emplacement " << &nights << endl;
    cout << "int ";
    cout << "valeur = " << *pt << ": emplacement = " << pt << endl;

    double * pd = new double; // allouer de l'espace pour un double
    *pd = 10000001.0; // y stocker un double

    cout << "double ";
    cout << "valeur = " << *pd << ": emplacement = " << pd << endl; 
    cout << "emplacement du pointeur pd : " << &pd << endl;
    cout << "taille de pt = " << sizeof(pt);
    cout << ": taille de *pt = " << sizeof(*pt) << endl;
    cout << "taille de pd = " << sizeof pd;
    cout << ": taille de *pd = " << sizeof(*pd) << endl;

    return 0;
}

Et maintenant l'auteur a fait une note sur le code :

Un autre point à noter est que généralement new utilise un bloc de mémoire différent des définitions de variables ordinaires que nous avons utilisées. La variable nights et pd ont toutes deux leurs valeurs stockées dans une région de mémoire appelée la pile, tandis que la mémoire allouée par le mot-clé new se trouve dans une région appelée le tas ou la mémoire libre.


Question Initiale:

Maintenant, ma préoccupation est la suivante : la variable pd a été créée avec le mot-clé new, elle devrait donc être stockée dans la région appelée tas comme la variable pt, car elles ont toutes deux été créées avec le mot-clé new.

Est-ce que j'ai manqué quelque chose ici ? Merci beaucoup d'avance pour vos réponses.

Question Révisée/Suite en réponse à la mise en attente:

Cette question a été mise en attente par 5 personnes parce qu'elles ne comprenaient pas ce que je demandais. Je crois que ma question a déjà été répondue, mais pour ceux qui ne sont toujours pas sûrs de ce que je demandais initialement, veuillez lire attentivement :

J'étais confus quant à l'explication de l'auteur sur l'endroit où les variables et leurs valeurs étaient stockées en mémoire. Jusqu'à l'explication de l'auteur, j'avais la conviction que toute mémoire créée dynamiquement (ou devrais-je dire pendant l'exécution après la compilation) en utilisant le mot-clé new était stockée dans le tas et non dans la pile.

Donc, j'étais perplexe lorsqu'il a écrit que la variable pd avait sa valeur stockée dans la pile, mais encore une fois comment est-ce possible si la variable a été créée pendant l'exécution avec le mot-clé new, elle devrait donc être dans le tas et non la pile.

Veuillez essayer d'utiliser le code ci-dessus comme référence et en particulier les **variables (nights, pd et pt)) dans votre réponse pour que je puisse le comprendre du point de vue de ce code.

0 votes

int * pt = nouveau int; pourquoi ne pas simplement int t = 1001;?

26 votes

Vous ne manquez pas grand-chose. Et l'auteur du livre mélange les détails d'implémentation avec la sémantique du C++ lui-même. Je suggère de trouver un meilleur livre.

33 votes

pd est sur la pile (tenant un pointeur), ce à quoi pd pointe est sur le tas.

94voto

Lundin Points 21616

Les variables de pointeur pt et pd sont stockées sur la pile. Les valeurs auxquelles elles pointent, allouées avec new, sont stockées sur le tas.

Si j'écris une enseigne avec une flèche portant l'étiquette "lac", cela ne signifie pas que l'enseigne elle-même est un lac, ni qu'elle doit être montée dans un lac. Au contraire, elle doit être montée sur un sol solide, pointant dans la direction du lac.

9 votes

Excellente analogie du lac. Je vais la voler!

20 votes

Chipoter : Le standard c++ ne mentionne pas du tout le concept d'une pile ou d'un tas. Ils ont un concept de stockage local et dynamique si je me souviens bien.

0 votes

@ Lundin J'aime les analogies. Merci pour ça. Juste une question rapide pour solidifier ma compréhension : Les noms de variables réels.... "pt", "pd" et "night" sont tous stockés dans la pile. Juste la variable non ? Maintenant, la valeur qu'ils transportent ou tiennent est stockée où ? Je m'excuse si ma question est redondante. Si quelqu'un peut faire quelque chose comme ça je serais heureux : variable nights --> Pile ou Tas ? | la valeur de nights ---> ? variable pd --> Pile ou Tas ? | la valeur de pd ---> ? variable pt --> Pile ou Tas ? | la valeur de pt -----> ?

19voto

Bathsheba Points 23209

Les seules erreurs commises par l'auteur sont

  1. Ne pas appeler delete une fois que l'utilisation des pointeurs est terminée.

  2. L'utilisation répétée de endl est discutable. Utilisez plutôt \n.

  3. using namespace std;, bien que souvent utilisé dans les tutoriels pour gagner en brièveté, est déconseillé dans le code de production, notamment dans les en-têtes.

  4. L'utilisation de int * pt = new int; *pt = 1001; au lieu de int* pt = new int(1001); est discutable car, de la manière dont l'auteur l'a fait, *pt se trouve dans un état non initialisé de manière inutile entre deux instructions. Cela rend votre code vulnérable à l'instabilité.

  5. (Mineur) Vous devriez toujours penser à la possibilité que new puisse provoquer une exception std::bad_alloc en cas d'échec de l'allocation.

Je ne m'inquiéterais pas trop des termes stack et heap; ce sont des concepts d' implémentation du langage et non pas du langage lui-même. Préférez les termes durée de vie de stockage automatique et dynamique. C'est ce que spécifie la norme C++, alors pourquoi inventer tout un tas de terminologie alternative?

Sérieusement, je brûlerais le livre et je me procurerais une copie de Stroustrup.

0 votes

J'ai obtenu mon vote pour avoir dirigé l'OP vers la durée de stockage, seule. Réponse fantastique.

0 votes

@Bathsheba Merci pour la réponse!

12 votes

Je ne suis pas sûr de être d'accord avec #5. Le point entier de lancer une exception (par opposition à la retour d'un code d'erreur, de style C) est que vous n'avez pas à écrire de code de traitement pour l'exception. Si une tentative d'allocation de mémoire échoue—ce qui est un événement inattendu dans le cas typique, et vous ne pouvez même pas écrire un gestionnaire si vous le vouliez—alors l'exception est lancée et le programme est terminé.

16voto

John Bode Points 33046

Les images aideront.

Stockage automatique (pile)                      Stockage dynamique (tas)
-------------------------                      ----------------------

Élément     Adresse        Valeur               Adresse        Valeur
------      -------        -----               -------        -----          
nights      0xff001000     1001               
    pt      0xff001004     0x00b0fff0 ------>  0x00b0fff0     1001
    pd      0xff00100c     0x00b0fff4 ------>  0x00b0fff4     10000001.0            

Les objets nights, pt et pd ont tous une durée de stockage auto. Sur la plupart des implémentations, cela signifie qu'ils sont alloués à partir de la pile d'exécution. L'objet nights réside à l'adresse 0xff0010001 et stocke la valeur 1001. L'objet pt réside à l'adresse 0xff0010004 et stocke l'adresse d'un objet dynamique créé par new, qui est 0x00b0fff0. L'objet pd réside à l'adresse 0xff00100c et stocke l'adresse d'un autre objet dynamique créé par new, qui est 0x00b0fff4.

Les objets du tas aux adresses 0x00b0fff0 et 0x00b0fff4 stockent respectivement les valeurs 1001 et 10000001.0.

Éditer

Pour ce que ça vaut, j'ai écrit un utilitaire dumper il y a quelque temps qui affiche les adresses et les contenus des objets; étant donné le code

#include 
#include "dumper.h"

using namespace std;

int main( void )
{
  int nights = 1001;
  int *pt = new int;
  *pt = 1001;
  double *pd = new double;
  *pd = 1000001.0;

  char *names[] = { "nights", "pt", "pd", "*pt", "*pd" };
  void *addrs[] = { &nights, &pt, &pd, pt, pd };
  size_t sizes[] = { sizeof nights, sizeof pt, sizeof pd, sizeof *pt, sizeof *pd };

  dumper( names, addrs, sizes, 5, stdout );

  return 0;
}

J'obtiens la sortie

       Élément         Addresse   00   01   02   03
       ------         -------   --   --   --   --
     nights  0x7fff9efe7c6c   e9   03   00   00    ....

         pt  0x7fff9efe7c60   10   20   50   00    ..P.
             0x7fff9efe7c64   00   00   00   00    ....

         pd  0x7fff9efe7c58   30   20   50   00    0.P.
             0x7fff9efe7c5c   00   00   00   00    ....

        *pt        0x502010   e9   03   00   00    ....

        *pd        0x502030   00   00   00   00    ....
                   0x502034   82   84   2e   41    ...A

Dans ce cas, les adresses sont réelles. Sur mon système (x86_64/Linux SLES-10), les adresses de la pile commencent haut et diminuent "vers le bas" (vers des adresses plus basses), tandis que les adresses du tas commencent bas et augmentent "vers le haut" (vers des adresses plus élevées).

x86 est petit boutiste, ce qui signifie que l'octet adressé est le byte le plus faible; les objets multi-octets doivent être lus de droite à gauche.


  1. Toutes les adresses sont inventées de toutes pièces et ne visent pas à représenter une implémentation ou une architecture du monde réel.

0 votes

Vous voudrez peut-être rendre vos adresses de pile et de tas plus clairement différentes, car pendant un moment j'ai pensé que pt pointait vers lui-même.

0 votes

@JohnBode Merci mec! C'était très bien fait. Au fait, comment as-tu construit cette image? Est-ce une fonctionnalité de l'IDE qui l'a produite pour toi après la compilation du code source ou quoi? Beaucoup de bénédictions sur ton chemin.

0 votes

@rnd809: Tout tapé manuellement, formaté comme un extrait de code (essentiellement veillé à ce que chaque ligne ait quatre espaces en tête).

15voto

David Schwartz Points 70129

Maintenant, mon souci est le suivant : la variable pd a été créée par le mot-clé new, donc elle devrait être stockée dans la zone appelée tas tout comme la variable pt, puisqu'elles ont toutes deux été créées par le mot-clé new.

Non, ce n'était pas le cas. Le code est :

 double * pd = new double; // alloue de l'espace pour un double

Cela ne diffère pas de :

double * pd;
pd = new double;

Il est donc évident que pd lui-même n'est pas créé par l'opérateur new, seulement la valeur qu'il contient l'est. Mais il parle de pd, pas de sa valeur.

2 votes

L'auteur parle de valeurs. Donc, oui, la valeur de pd est une adresse et elle est stockée sur la pile. Mais sa valeur est une adresse qui pointe vers la mémoire sur le tas.

1 votes

@zoska L'auteur parlait en effet de valeurs. Donc comme vous l'avez dit, "pd est une adresse et elle est stockée sur la pile. Mais sa valeur est une adresse qui pointe vers la mémoire sur le tas." Je pense que cela clarifie les choses.

0 votes

@DavidSchwartz Merci monsieur!

7voto

mapreduce_kaz Points 88

La variable pd est stockée dans la mémoire de la pile, cependant l'emplacement mémoire pointé par pd est alloué dans la mémoire de tas. Je pense que l'auteur voulait mettre en avant précisément cette différence entre ces concepts.

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