试图理解 C++ 中的堆栈和堆

Posted

技术标签:

【中文标题】试图理解 C++ 中的堆栈和堆【英文标题】:Trying to understand stack and heap in C++ 【发布时间】:2013-06-02 13:53:37 【问题描述】:

我对 C++ 还是很陌生,并试图尽可能多地理解堆栈和堆的概念(或者至少我需要知道的尽可能多的知识)。 有些人倾向于说启动器不应该那么麻烦,但在我看来,内存泄漏或堆栈溢出很容易发生。 我一直在阅读一些东西,但我仍然有点困惑,不确定我是否正确。

这是我目前得到的...

1.堆:

堆是一个共享和动态分配的区域。 我们流程的任何部分都可以通过正确的指针和内容(类型和长度)知识来访问它。 如果我们尝试使用错误的指针(普通地址或释放指针)将导致分段错误。 访问比分配的内容更大的内容也会导致分段错误(例如,尝试读取比分配的更大的数组)。 未使用的区域必须“手动”释放以避免内存泄漏

2。堆栈:

堆栈是分配参数和局部变量的内存部分。 堆栈的大小是有限的。 堆栈作为 LIFO(后进先出)工作。

假设堆栈是一个预定义大小(堆栈大小)的 bin。 当我们定义局部变量时,它们被放入堆栈(放入 bin),并且一旦作用域发生变化(例如调用一个函数),我们的 bin 中就会使用一个盘片来防止访问先前作用域中定义的变量和一个新的局部变量范围被创建。 一旦函数结束,所有局部变量都会被销毁,并且我们 bin 中的盘片会被移除(返回到之前的作用域)。

示例:

void MyFunction()

    int *HeapArray = new int[10];
    // HeapArray is assigned 40 bytes from the heap
    // *HeapArray is assigned 4 bytes from the stack in a 32 bit environment

    int StackArray1[10];
    // StackArray is assigned 40 bytes from the stack

    int StackArray2[20];
    // StackArray is assigned 80 bytes from the stack

    HeapArray = StackArray2;
    // segmentation fault because StackArray it too large

    delete HeapArray;
    // this will deallocate the area assigned in the heap
    // omitting delete would result in memory leaks
    // the pointer itself *HeapArray continues to exist in the stack

    HeapArray = StackArray1;
    // segmentation fault because HeapArray is pointing to deallocated memory

    MyFunction();
    // this will result in a stack overflow


问题:

第一季度。定义一个对于堆栈来说太大的局部变量或者像我上面的示例那样具有无限递归函数会给我一个分段错误。为什么这不是说“堆栈溢出”?是不是因为堆栈“溢出到堆中”并造成分段错误?

第二季度。假设我为堆栈提供的 bin 和 platters 示例:使用 extern 时,内容是复制到最后一个盘子顶部的新范围还是创建了某种指针?

【问题讨论】:

【参考方案1】:

您发布的代码充满了错误,而不仅仅是您在 cmets 中列出的那些。与之相伴的是,纯 C 风格的数组是不可分配的。所以,下面这行并不是将右边数组的内容复制到左边的数组中。

HeapArray = StackArray2;

C 和 C++ 允许从数组隐式转换为指向数组第一个元素的指针;这通常被称为 decaying 指向第一个元素的指针。因此,上面的语句导致HeapArray 指针指向StackArray2 的开头。然后,当您在HeapArray 上调用delete 时,您正在尝试delete 不是newed 的内存。这是未定义的行为,会导致程序崩溃(如果幸运的话)。

除此之外,您还泄露了您通过new 分配的内存,因为您现在丢失了指向该内存的唯一指针。

类似地,HeapArray 的下一个分配是将StackArray1 的地址分配给HeapArray。因为你只是分配指针,这一行没有错误;程序将继续正常执行(但您可能已经因为之前的删除而崩溃了)。


回答你的问题 -

1 - 无法保证堆栈溢出或错误删除总是会以可预测的方式失败。它还取决于您使用的编译器。如果我注释掉MyFunction() 中的所有代码,除了对自身的递归调用,g++ 4.8 不会发出任何警告,并且会因分段错误而失败。但是,VS2012 会发出警告

警告 C4717: 'MyFunction' : 在所有控制路径上递归,函数将导致运行时堆栈溢出

并在运行时失败说明

Test.exe 中 0x00062949 处未处理的异常:0xC00000FD:堆栈溢出(参数:0x00000001、0x00802FA4)

但是,那是在禁用优化的情况下。启用优化后,程序会无限运行(使用两个编译器)。这可能是因为代码足够简单,以至于两个编译器都用无限循环替换了对 self 的递归调用。现在不会出现堆栈溢出,但您的程序也永远不会终止。

2 - 全局变量(您 extern 从另一个翻译单元访问的变量)不存储在堆栈中。它们存储在与堆栈不同的实现定义的内存区域中。

【讨论】:

谢谢你的回答,我完全忘记了 extern 使用的东西实际上是全局的,它们驻留在数据段中。 关于我的示例代码:我仍然需要习惯使用指针。在这种情况下复制数组的最佳方法是什么? memcpy? std::copy? @DanielP 在 C++ 中,很少需要调用 memcpy。调用std::copy,您的标准库实现应该在幕后调用memcpy,以获得简单的可复制类型。最后,对于数组,如果您在编译时知道大小,您真的应该使用std::array,如果您需要在运行时设置大小,则应该使用std::vector。如果你真的负担不起std::vector 带来的一些额外指针,那么std::unique_ptr<T[]> 总是有的。【参考方案2】:

第一季度。发生的事情称为堆栈溢出,但结果是您超出了堆栈允许的内存,因此您试图访问您无法访问的内容,因此从技术上讲它是一个分段故障

第二季度。您的示例不适合,因为堆栈没有隐藏任何先前的内容,任何指向堆栈深处的任何内容的指针仍然有效,它不会被连续调用隐藏。之所以称为堆栈,是因为它的行为类似于堆栈(分配的数据在其顶部增长),但在其下方的任何内容都只是内存,如果您保留指向它的指针,则可以合法访问。

补充说明一下,调用栈还包含函数调用的激活记录,用于能够正确返回。

【讨论】:

感谢您的回复,我刚刚意识到我的第二个问题有点奇怪,因为它实际上是关于全局变量

以上是关于试图理解 C++ 中的堆栈和堆的主要内容,如果未能解决你的问题,请参考以下文章

java 堆栈 理解

Swift 栈和堆的理解

C++ 中的 RAM、硬盘、堆栈和堆是啥?

调用堆栈和堆栈有啥区别?

C++ 中堆栈、静态和堆内存的最大内存

堆栈和堆内存的大小[重复]