2

它只是那些“这就是语言的工作方式”问题之一吗?编辑:

为什么动态内存允许在运行时分配数组的大小?

为什么我不能只使用从堆栈调用的变量,而不是从堆调用的变量?它们都是变量,一个只是从不同的地方调用,必须手动释放和创建。在堆栈中创建的变量可以在运行时更改,对吗?

4

3 回答 3

4

为什么我不能只使用从堆栈调用的变量,而不是从堆调用的变量?

堆分配使您可以更好地控制内存。

此外,在堆栈变量方面也存在限制。


//警告:仅适用于个人电脑,可能不适用于我不熟悉的其他架构。(mips、motorola 68000 等。基本上与 x86 无关的任何东西)。

在堆栈中创建的变量可以在运行时更改,对吗?

他们的 SIZE 不能改变。

  1. 堆栈的大小有限。大小由操作系统和编译器决定。如果堆栈变得太大,程序会因为堆栈溢出而死掉。经典例子:

    int main(int argc, char** argv){
        char buffer[1024*1024*64];
        buffer[0] = 0;
        return 0;
    }
    

    如果在 windows 上使用默认设置编译该程序会崩溃,并且在 linux 上也会崩溃。这是因为默认堆栈大小在 32 位 Windows 上为 1 MB,在 32 位 Linux 上为 8 MB(系统可能会更改这一点,但使用ulimit),并且 64 兆字节的数组不适合堆栈。

  2. 如果您的变量位于堆栈上的其他两个变量之间,则无论如何您都无法更改其大小。至少在 x86/64 CPU 上。

  3. 理论上,如果堆栈数组是堆栈上的最后一件事,您可以增加堆栈数组的大小。(如果我没记错的话,可能有一个名为 alloca 的非标准 C 函数可以在堆栈上分配数组)。但是,您仍然会达到堆栈大小限制。

要了解为什么会有这样的限制,您需要从 C++ 退一步,并学习一点汇编。尝试找到一本涵盖段(数据/代码/堆栈)的书,解释函数返回地址的存储位置,并且最好告诉您有关保护模式的信息。那应该有帮助。

当然,有一点问题。汇编知识仅对特定系列的 CPU 有所帮助。带有 C++ 编译器的不同 CPU 可能使用不同的规则。

- 更新 -

为什么动态内存允许在运行时分配数组的大小?

根据拱门,堆栈大小可能或多或少是固定的。您为堆栈保留了一些内存区域,例如地址 0x00100000..0x00200000,并且位于堆栈上的所有变量都将位于该区域的某个位置。新变量的位置由(如果我没记错的话)“堆栈指针”确定,它的移动方向由 CPU 确定。当您在堆栈上添加新变量时,堆栈指针会按照大小变量移动(移动方向由 CPU 确定),并且变量将位于新旧内存位置之间的地址中。因为堆栈空间是有限的,并且因为变量是相邻的(加上函数返回地址也存储在堆栈上),所以你不能突然将 2GB 数组塞在其中。主要问题实际上不是限制大小,而是变量彼此相邻。

现在,HEAP 不同了。理论上,堆分配可以返回整个地址空间中的任何地址,但实际上会保留一些地址(例如,在 32 位窗口上,整个 4GB 空间中只有 2..3GB 可用)。因为您有更多可用空间,并且分配的块不必相邻,所以您可以自由分配大数组并且(理论上)甚至调整它们的大小(实际上像 realloc 这样的函数可能只是创建新数组,将旧内容复制到新数组,然后杀死旧数组)。

请注意,还有其他隐藏的细节。例如,堆分配函数返回给您的地址不是物理地址,而是虚拟地址,实际上操作系统可以在物理内存中移动您的程序,而程序中使用的(虚拟)地址将保持不变。

这就是为什么我建议阅读汇编书。你不必深入学习汇编,但对幕后发生的事情有一些大致的了解,肯定会有所帮助。

于 2013-06-22T12:45:43.900 回答
3

使用堆的主要原因不是你可以使用可变数量的它。主要用途是它允许内存在特定功能完成后持续存在。

C99 及更高版本的标准(但不是 C++,尽管我相信某些编译器(g++?)确实有允许它的扩展,至少有时)允许函数中的“可变长度数组”,但就像固定大小的数组一样,它们“消失”当函数结束时。

在 C++ 标准中,所有数组都必须具有“编译时已知(恒定)长度”。您必须使用堆来创建不是恒定长度的东西,在编译时就知道了。这是“语言的工作方式”。

话虽如此,这也有合理的理由。堆栈空间非常有限,实际上用完堆栈是非常“危险的”,因为程序对此无能为力——它崩溃了,并且没有安全/合理的恢复方式。堆空间用完可以处理(抛出了一个 C++ 异常,但至少程序可以显示一些合理的错误消息,并且可能以某种方式继续,即使它在运行时尝试执行的操作没有成功堆空间不足)。

当然,“C++ 方式”是不编写手动操作数组大小的代码,而是使用预定义的容器类型之一,例如std::vector等等。

编辑 请注意,一旦从堆中分配了一个数组,它就会保持分配时的大小。改变它的大小可以做的是为第二个数组分配另一块不同大小的内存,然后将“旧”数组的内容复制到“新”数组中——只要这是以代码只能看到数组地址[指向第一个元素的指针]的“当前”值的方式完成,没有人会知道它不是同一个数组。

于 2013-06-22T12:37:01.900 回答
0

堆栈的大小是有限的,当您的函数返回时,堆栈上的任何内容都会消失,因为堆栈指针已更改。堆内存可以更大,只要应用程序正在运行或有人调用函数来释放内存,它就会一直存在。

但是在 Windows 上,仍然可以使用 alloca 在堆栈上分配“动态大小”的内存。这将以与清除任何其他基于堆栈的本地变量相同的方式被清除,因此您不必显式释放内存。

见这里: http: //msdn.microsoft.com/en-US/library/wb1s57t5 (v=vs.80).aspx

于 2013-06-22T13:13:18.170 回答