【问题标题】:Did the author make a mistake in explaining stack and heap in C++ or am I misreading something?作者在解释 C++ 中的堆栈和堆时是否犯了错误,还是我误读了什么?
【发布时间】:2017-06-08 07:58:31
【问题描述】:

代码如下:

int main()
{
    using namespace std; 
    int nights = 1001; 
    int * pt = new int; // allocate space for an int
    *pt = 1001;         // store a value there

    cout << "nights value = ";
    cout << nights << ": location " << &nights << endl;
    cout << "int ";
    cout << "value = " << *pt << ": location = " << pt << endl;

    double * pd = new double; // allocate space for a double
    *pd = 10000001.0; // store a double there

    cout << "double ";
    cout << "value = " << *pd << ": location = " << pd << endl; 
    cout << "location of pointer pd: " << &pd << endl;
    cout << "size of pt = " << sizeof(pt);
    cout << ": size of *pt = " << sizeof(*pt) << endl;
    cout << "size of pd = " << sizeof pd;
    cout << ": size of *pd = " << sizeof(*pd) << endl;

    return 0;
}

下面是作者对代码的注释:

需要注意的另一点是,通常 new 使用与我们一直使用的普通变量定义不同的内存块。变量 nightspd 的值都存储在称为 stack 的内存区域中,而由 new 位于称为堆或空闲存储区的区域中。


最初的问题:

现在我担心的是:变量 pd 是由关键字 new 创建的,所以它应该存储在名为 heap 的区域中就像变量 pt 一样,因为它们都是由关键字 new 创建的。

我在这里遗漏了什么吗?非常感谢您的意见。

基于保留的修订问题/跟进:

这个问题被 5 个人搁置了,因为他们不明白我在问什么。我相信我的问题已经得到解答,但是对于那些仍然不确定我最初问什么的人,请继续阅读:

我不清楚作者关于变量及其值在内存中的存储位置的解释。根据作者的解释,我相信使用关键字 new 动态创建的任何内存(或者我应该说是在编译后的运行时)都会存储在 heap 中,而不是堆栈

所以,当他写到 pd 的变量是 value 时,我感到很困惑 存储在 stack 中,但如果变量 是在“运行时”使用关键字 new 创建的,所以它应该是 在,而不是堆栈

请尝试使用上面的代码作为参考,尤其是您的答案中的**变量(nights、pd 和 pt),以便我可以从该代码的角度理解它。

【问题讨论】:

  • 您并没有错过太多。本书的作者将实现细节与 C++ 本身的语义混合在一起。我建议买一本更好的书。
  • pd 在栈上(持有指针),pd 指向的在堆上。
  • -6 对于具有可编译代码的编写良好的问题特别苛刻。有什么办法可以改进这个问题?这本书是由布偶写的这一事实与 OP 无关,除了他们的选书技巧。
  • 我在 Google 上搜索“是堆栈或堆上的指针”,第一个结果是 this question,它回答了 OP 关于堆栈/堆上的指针的直接问题。因此,缺乏研究可能是一个有效的主张。
  • 看看here

标签: c++ c++11


【解决方案1】:

指针变量ptpd 存储在堆栈中。他们指向的值,用新分配的,存储在堆上。

如果我写了一个标有“湖”箭头的标志,这并不意味着标志本身就是一个湖,也不意味着它必须安装在湖中。相反,它应该安装在坚实的地面上,指向湖的方向。

【讨论】:

  • 优秀的湖类比。我要去偷它!
  • 吹毛求疵:c++ 标准根本没有提到 stackheap 的概念。他们有一个本地和动态存储 IIRC 的概念。
  • @πάνταῥεῖ 那又怎样?如果 C++ 标准没有提到它就不存在?标准中没有提到堆栈和堆的原因仅仅是因为这些东西超出了编程语言的范围。无论如何,所有现实世界的计算机都使用称为堆栈和堆的段。 (当然,局部变量不一定存储在堆栈中,它们可以存储在寄存器中。)
  • @πάνταῥεῖ C++ 标准到处都提到了 stack 这个词。 Free Store 只是 heapC++ 术语,概念相同,但 heap 是用于获取任意内存块的位置的通用计算术语。它与严格 FILO 组织的stack 形成鲜明对比。
  • c++ 的阴暗面是语言警察。
【解决方案2】:

作者犯的唯一错误是

  1. 一旦使用完指针,​​就不会调用delete

  2. 重复使用endl 是有问题的。请改用\n

  3. using namespace std;,虽然经常用于教程中以实现简洁,但在生产代码中是不明智的,尤其是在标头中。

  4. 使用int * pt = new int; *pt = 1001; 代替int* pt = new int(1001); 是有问题的,因为作者拥有它的方式,*pt 在两个语句之间处于不必要的未初始化状态。这会使您的代码容易受到不稳定因素的影响。

  5. (次要)。您应该始终考虑如果分配失败,new 可能会抛出 std::bad_alloc 异常。

我不会太担心术语 stackheap;它们是语言实现概念不是语言本身的一部分。首选术语自动动态存储期限。这就是 C++ 标准所使用的,那么为什么要发明一大堆替代术语呢?

真的,我会烧掉这本书,给自己买一本 Stroustrup。

【讨论】:

  • 我不确定我是否同意#5。抛出异常(与返回错误代码、C 风格相反)的全部意义在于您没有必须 为异常编写处理代码。如果尝试分配内存失败——这是典型情况下的意外事件,如果你愿意,你甚至无法编写处理程序——然后抛出异常并终止程序。
  • + 关于烧掉这本书的最后一条评论 - 这本书似乎在教一系列可能无法通过同行评审的坏习惯。
  • 这本书变得更糟了。为什么它会在new 之后引入delete,并在其间添加一些代码?除非代码被水印“绝对垃圾”,否则这种行为是应受谴责的。如果被那些习惯于使用垃圾收集器的语言的人阅读,则更是如此。
  • 为什么反对使用endl?这不是编写适合操作系统的端行的工具吗?请记住,windows 使用 CRLF,因此 '\n' 不是正确的行尾。
  • @Benubird \n 无论操作系统如何都是正确的;我们有文本模式和二进制模式的部分原因是抽象出行尾差异。 (而endl 所做的只是写一个\n,然后是一个刷新。)
【解决方案3】:

图片会有所帮助。

Automatic storage (stack)                      Dynamic storage (heap)
-------------------------                      ----------------------

Item        Address        Value               Address        Value
----        -------        -----               -------        -----          
nights      0xff001000     1001               
    pt      0xff001004     0x00b0fff0 ------>  0x00b0fff0     1001
    pd      0xff00100c     0x00b0fff4 ------>  0x00b0fff4     10000001.0            

对象nightsptpd 都具有auto 存储持续时间。在大多数实现中,这意味着它们是从运行时堆栈中分配的。对象nights 位于地址0xff0010001 并存储值1001。对象pt 位于地址0xff0010004 并存储new 创建的动态对象的地址,即0x00b0fff0。对象pd 位于地址0xff00100c,并存储了由new 创建的另一个动态对象的地址,即0x00b0fff4

地址0x00b0fff00x00b0fff4 的堆对象分别存储值100110000001.0

编辑

FWIW,我前段时间写了一个dumper 实用程序,用于转储对象地址和内容;给定代码

#include <cstdio>
#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;
}

我得到了输出

       Item         Address   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

在这种情况下,地址真实的。在我的系统 (x86_64/Linux SLES-10) 上,堆栈地址从高开始并“向下”增长(朝向较低地址),而堆地址从低开始并“向上”增长(朝向较高地址)。

x86 是小端,意味着寻址字节是最低有效字节;多字节对象需要从右向左读取。


  1. 所有地址都是凭空捏造的,并不代表任何现实世界的实现或架构。

【讨论】:

  • 你可能想让你的栈和堆地址更明显不同,有那么一刻我以为pt指向它自己。
  • @JohnBode 谢谢兄弟!这做得非常好。顺便说一句,你是如何构建这个图像的?它是在编译源代码后为您生成它的 IDE 功能还是什么?许多祝福你的方式。
  • @rnd809:手动全部输入,格式化为代码 sn-p(基本上确保每行有四个前导空格)。
  • @rnd809:添加了一些代码输出,显示了我系统上内容的实际地址。
  • @JohnBode 明白了!那是金子。
【解决方案4】:

现在我担心的是:变量 pd 是由关键字 new 创建的,因此它应该像变量 pt 一样存储在称为 heap 的区域中,因为它们都是由关键字 new 创建的。

不,不是。代码是:

 double * pd = new double; // allocate space for a double

这与:

double * pd;
pd = new double;

很明显,pd 本身不是由new 运算符创建的,只有它所持有的值才是。但他说的是pd,而不是它的价值。

【讨论】:

  • 作者讲的是价值观。所以,是的,pd 的值是一个地址,它存储在堆栈中。但它的值是指向堆内存的地址。
  • @zoska 作者确实在谈论价值观。所以就像你说的,“pd 是一个地址,它存储在堆栈上。但它的值是一个指向堆内存的地址。”我认为这可以解决问题。
  • @DavidSchwartz 谢谢你,先生!
  • @zoska 作者不是在谈论价值观。假设您有一个变量j,其值为 5,地址为 10。如果您说“存储值的位置”,您指的是 10,而不是 5。文本是“将它们的值存储在其中”,所以不是谈论值,但该值存储在哪里,即保存该值的变量本身。如果你说我的车存放在哪里,你说的是我的车库,而不是我的车。
【解决方案5】:

pd 变量存储在堆栈内存中,但是pd 指向的内存位置在堆内存中分配。我认为作者想准确地强调这些概念之间的差异。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-11-14
    • 2012-08-06
    • 2017-10-12
    • 2021-10-13
    • 2016-12-09
    • 2022-10-13
    • 1970-01-01
    • 2022-01-16
    相关资源
    最近更新 更多