为什么动态内存允许在运行时操作数组?

时间:2013-06-22 12:28:22

标签: c++ c memory dynamic

这只是其中一种“这就是语言运作方式”的问题吗? 编辑:

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

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

3 个答案:

答案 0 :(得分:4)

  

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

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

此外,在堆栈变量方面也有局限性。


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

  

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

他们的SIZE无法改变。

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

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

    如果使用Windows上的默认设置进行编译,该程序将崩溃,并且它也会在linux上崩溃。这是因为32位窗口上的默认堆栈大小为1 MB,32位linux上的默认堆栈大小为8 MB(系统可能会使用ulimit更改此值),而64 MB的数组将不适合堆栈。

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

  3. 理论上,如果它是堆栈中的最后一个东西,你可以增加堆栈数组的大小。 (如果我没记错的话,可能有非标准的C函数叫做alloca,可以在堆栈上分配数组)。但是,您仍将达到堆栈大小限制。
  4. 要了解为何存在此类限制,您需要退回C ++,并学习一些程序集。尝试找到一本涵盖段(数据/代码/堆栈)的书,解释存储函数返回地址的位置,最好告诉您有关保护模式的信息。这应该会有所帮助。

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

    - 更新 -

      

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

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

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

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

    这就是我建议阅读装配书的原因。你不必深入学习装配,但对幕后发生的事情有一些大概的了解,肯定会有所帮助。

答案 1 :(得分:3)

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

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

在C ++标准中,所有数组必须在编译时具有“已知(常量)长度”。您必须使用堆来创建长度不等的内容,这在编译时是已知的。这是“语言运作的方式”。

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

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

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

答案 2 :(得分:0)

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

然而,在Windows上,仍然可以分配"动态大小"使用alloca在堆栈上的内存。这将被清除,就像清除任何其他基于堆栈的本地一样,因此您不必显式释放内存。

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