【问题标题】:When does an object gets its memory?一个对象什么时候获得它的内存?
【发布时间】:2018-07-17 18:11:46
【问题描述】:

我有这样的功能:

1  int main(){
2    int n,m; cin>>n>>m; // Only for illustration
3    vector<int> v[(int) 1e8] ;
4    // ...
5  }

由于第 3 行引起的堆栈溢出,我会出现分段错误。因为,我正在学习 gdb,所以我在 main() 和第 2 行设置了一个断点。

我期待程序在第 3 行之前不为对象分配内存,并且第 1 行和第 2 行应该正确运行。在运行第 3 行之后必须发生分段错误。 唉!运行程序时,在第 1 行之后导致分段错误。我使用了info locals,它给出了:

v= <error reading variable v (value requires 2400000000 bytes, which 
    is more than max-value-size)>

这意味着程序进入main后试图分配内存,但是为什么会这样呢? 我认为变量的范围来自声明它的行,否则在声明之前使用它会出错。 我用它来编译:

g++ -g temp.cpp -o temp -O0

编辑

  1. 这个巨大的向量是故意的,因为我正在“学习”使用 gdb。但是后来我遇到了这个概念错误。

  2. 我将问题从 cmets 中指出的巨大向量 (1e10) 恢复为向量数组。

【问题讨论】:

  • 您要分配一个vector 的100,000,000 个ints 吗?目前,您正在尝试分配 100,000,000 vector&lt;int&gt;s
  • 您正在分配一个 array 向量。进入时为此分配空间。这显然超出了最大堆栈段大小。它还没有到运行第 3 行发生的向量的构造函数的地步。
  • 尝试使用vector&lt;int&gt; v(100000000); 或查看reserve 方法。注意()[] 之间的区别。后者表示数组。
  • @madhur4127 throwing an instance of 'std::bad_alloc'stack overflow 完全不同。此外,您是否在修改后检查异常是否在您最初预期的行抛出?我希望您的结果会有所不同。
  • 我敢打赌,修改后上面的错误信息并不是完全不相关的。此外,当您停在第 2 行时,您不会抛出 bad_alloc。在我看来,上述更改完全取消了这个问题(你应该问一个新问题)。

标签: c++ memory gdb stack-overflow


【解决方案1】:
 vector<int> v[(int) 1e8];

我原以为程序不会在第 3 行之前为对象分配内存

这是一个错误的假设。

标准没有明确说明何时获取自动变量的存储。它可以在块语句的开头获取,尽管编译器可能会将分配从内部块提升到外部块。事实上,如果编译器将函数调用内联展开,自动变量可能在执行到达函数调用表达式之前就被分配了。

我认为变量的范围是从它声明的那一行开始

没错。但是,对象的存储可能比对象的生命周期更长。

这意味着程序进入main后试图分配内存,但是为什么会这样呢?

在函数开始时分配一次函数的所有堆栈对象通常是最有效的。栈分配无非就是将分配对象的大小加到栈指针上。

【讨论】:

  • 这很有帮助。
  • @OP 我会注意到局部变量的空间分配和变量的生命周期不是一回事。生命周期是从建造到毁灭。局部变量的空间分配可以在分配之前的任何时候完成(但通常在函数入口处)。
【解决方案2】:

在堆栈上创建变量是很常见的(但据我所知也不能保证)在执行任何其他语句之前在每个块中创建所有内容。而且由于程序不能在堆上保留 1e8 个向量实例,因此您的程序会因为保留堆栈而失败,并且在执行任何其他语句之前都会失败。

失败不在于创建向量类的实例本身,因为构造函数将在第 3 行被调用。但是如前所述,程序之前崩溃了,因为堆栈按要求变小了。

顺便说一句:只要执行结果与程序中写入的结果相同,编译器就可以对所有指令重新排序。

【讨论】:

  • 但我专门使用 -O0 标志来输出我编写的代码,而没有从编译器方面进行任何优化。据我所知-O0 是从源代码直接翻译机器代码。
  • @madhur4127 -O0 不保证没有优化。一些优化是如此微不足道,它们会自动发生(为寄存器着色变量,为多个变量重用内存等)。降低优化级别将防止一些昂贵的优化,但它不能保证(除非您在编译器文档中明确看到)没有优化。
【解决方案3】:

从一个角度来看,正确的答案是引用语言标准中关于对象生命周期的文本。

我认为对这里发生的事情进行较低级别的解释会更有用。你正在分配一个包含一亿个向量的数组。 (你确定你打算这样做,而不是一个具有一亿个元素的向量?)因为这是在main 内,编译器可以静态初始化它们,但在这种情况下,它正在尝试将它们从堆栈中分配出去。

如何做到这一点因目标而异。在大多数调用约定中,它是通过在调用函数时从堆栈指针中减去堆栈帧和所有局部变量所需的字节数来实现的。这比尝试在函数内设置第二个帧指针要高效得多,这就是为什么嵌套块内的任何局部变量通常也会在调用函数时分配其内存。

即使是一个空向量也需要相当数量的字节——至少,一个指针和一个size_t。因此,每个 8 或 16 个字节,取决于它是 32 位还是 64 位程序,乘以 1 亿,几乎是 1 GB 或 2 GB。在这种情况下,这比允许一个函数从堆栈中吞噬更多的内存,所以程序在尝试为main() 设置堆栈帧时会立即崩溃。

这与调用对象的构造函数是分开的,后者可能具有并依赖于副作用,因此不允许提前完成。您可以自己单独执行这两个步骤,方法是分配一个内存缓冲区,然后以该缓冲区为地址调用placement new

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-02-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-15
    相关资源
    最近更新 更多