7

在学习 C++(和 C)时,我对堆栈分配的工作有一些特别的疑问,我找不到解决方案:

  1. 堆栈分配是否隐式调用 malloc/free 函数?如果不; 它如何确保堆栈分配和堆分配之间没有冲突?

  2. 如是; C++ 中的堆栈分配是否也隐式调用 new/delete?如是; 重载类的新运算符会影响其堆栈分配吗?

它在 VC++ 中产生了令人困惑的结果;但由于 VC++ 并不完全符合标准(或者我听说过),我决定我最好在这里问...

4

7 回答 7

26

堆栈分配不使用 malloc/free 之类的东西。它使用一块称为程序堆栈的内存,它只是一段连续的内存。

有一个特殊的寄存器存储栈顶。当在堆栈上创建新对象时,顶部会升高,从而增加堆栈,当对象被释放(超出范围)时,顶部会降低,从而减少堆栈。

如果您尝试在堆栈上分配一个太大的对象或进行太深的递归,则顶部将超出堆栈的最大允许大小,这称为堆栈溢出。

注意:堆栈增长的实际方向(增加或减少地址)会因系统而异,但无论实际方向如何,总体思路都是相同的。

于 2009-03-26T12:44:37.687 回答
17

第一个问题的答案是否定的。堆栈根本不是从堆中分配的。

您应该首先阅读堆栈和堆是什么以及在哪里以了解基本概念。

于 2009-03-26T12:42:25.400 回答
6

堆栈分配通常根据 alloca() 或由编译器隐式完成。一个做得好的 alloca() 只需要很少的指令,完成后没有成本(甚至不需要)释放它。

您可以将指向由 alloca() 分配的内存的指针传递给任何其他需要指针的函数/方法。你绝不能返回由 alloca() 分配的指针。

以下是使用堆栈分配的一些优点缺点。

于 2009-03-26T12:47:09.647 回答
6

这里有一个很好的问题:

“它如何确保堆栈分配和堆分配之间没有冲突?”

几乎所有 C/C++ 实现中都有一个连续的地址空间,因此堆栈和堆分配的内存必须在该空间中共存。

尽管每次堆栈增长和收缩都不是通过单独的堆分配来完成的,但您仍然可以将堆栈视为从堆分配的单个大内存块。如果堆栈超出该块的边界,那么我们就会出现堆栈溢出(醒目的名字......有人应该在它之后命名一个网站)。

在多线程程序中,每次线程启动时,都必须为其分配一个新的堆栈,而当一个线程死亡时,堆栈可以被释放。malloc并且使用与/公开的相同的堆管理来分配这些全栈块是有意义的free

所以 - 非常近似地说 - 您可以将堆栈视为一种在堆中共存的对象。当一个线程启动时,一个完整的堆栈被malloc一次性全部 -ed,然后它被子分配,然后它free一次性得到 -d。

在 Windows 上,您可以(如果您喜欢危险地生活)自己调用相同的虚拟内存 API来了解堆栈并强制释放其中的虚拟页面。

于 2009-03-26T13:31:49.897 回答
5

在 C 和 C++ 中,有两种类型的内存分配“自动”,其中对象是在函数调用的生命周期内创建的,以及“动态”,其中一些内存由运行时提供的函数分配。

在绝大多数运行时实现中,自动对象是使用创建线程时操作系统提供的连续堆栈分配的。堆栈通常从高值地址开始,并按对象的大小递减。动态分配(C 中的 malloc,C++ 中的 new)使用操作系统请求的其他一些内存。由于操作系统知道堆栈正在使用的地址,它不会为动态请求分配相同的地址。由于动态区域是无序的,所以通常称为堆。

所以“堆栈”分配不是 malloc/free。C++ 中的自动对象调用构造函数和析构函数,但不调用 new 或 delete,因为 new 和 delete 也有管理动态内存的代码。

于 2009-03-26T12:55:28.580 回答
1

不,堆栈分配不调用 malloc/free。整个堆栈空间在程序开始时分配。在进入每个函数时,堆栈指针被推进到足以在堆栈上为“堆栈帧”留出空间,这将是堆栈分配的变量所在的位置。

于 2009-03-26T12:44:21.527 回答
1

Kepp 记住,“堆栈分配”是一个实现细节。不能保证堆栈用于自动存储。例如,传说中的 IBM 大型机没有(尽管有人告诉我他们更现代的机器有)。

于 2009-03-26T14:09:49.637 回答