为什么我们不能在堆栈上分配动态内存?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了为什么我们不能在堆栈上分配动态内存?相关的知识,希望对你有一定的参考价值。

在堆栈上分配内容非常棒,因为我们拥有RAII并且不必担心内存泄漏等问题。但是有时我们必须在堆上分配:

  • 如果数据真的很大(推荐) - 因为堆栈很小。
  • 如果要分配的数据大小仅在运行时已知(动态分配)。

两个问题:

  1. 为什么我们不能在堆栈上分配动态内存(即只在运行时已知的大小的内存)?
  2. 为什么我们只能通过指针引用堆上的内存,而堆栈上的内存可以通过普通变量引用?即Thing t;

编辑:我知道一些编译器支持可变长度数组 - 这是动态分配的堆栈内存。但这确实是一般规则的例外。我有兴趣理解为什么一般来说,我们不能在堆栈上分配动态内存的基本原因 - 技术原因和它背后的理性。

答案

为什么我们不能在堆栈上分配动态内存(即只在运行时已知的大小的内存)?

实现这一点要复杂得多。由于完成的可执行文件需要包含以便工作的指令类型,每个堆栈帧的大小都会被烧入已编译的程序中。例如,函数局部变量的布局和内容通过它在低级汇编代码中描述的寄存器和内存地址实际上硬编码到程序中:“变量”实际上并不存在于可执行文件中。让这些“变量”的数量和大小在编译运行之间发生变化会使这个过程变得非常复杂,尽管并非完全不可能(正如您所发现的那样,使用非标准的可变长度数组)。

为什么我们只能通过指针引用堆上的内存,而堆栈上的内存可以通过普通变量引用

这只是语法的结果。 C ++的“正常”变量恰好是具有自动或静态存储持续时间的变量。语言的设计者可以在技术上做到这一点,你可以写一些像Thing t = new Thing,只是整天使用t,但他们没有;再次,这将更难以实施。那你如何区分不同类型的物体呢?请记住,您编译的可执行文件必须记住自动销毁一种而不是另一种。

我很想详细说明为什么以及为什么这些事情都不困难,因为我相信这就是你在这里所追求的。不幸的是,我对装配的了解太有限了。

另一答案

为什么我们不能在堆栈上分配动态内存(即只在运行时已知的大小的内存)?

从技术上讲,这是可能的。但未经C ++标准批准。可变长度数组(VLA)允许您在堆栈内存上创建动态大小的构造。大多数编译器允许这作为编译器扩展。

例:

int array[n];

//where n is only known at run-time

为什么我们只能通过指针引用堆上的内存,而堆栈上的内存可以通过普通变量引用?即Thing t;

我们可以。您是否这样做取决于手头特定任务的实施细节。

例:

int i;
int *ptr = &i;
另一答案

我们可以使用函数_alloca在堆栈内存上动态分配可变长度空间。此函数从程序堆栈分配内存。它只需要分配字节数并将void *返回到已分配的空间,就像malloc调用一样。这个分配的内存将在函数退出时自动释放。

所以它不需要明确释放。在这里必须记住分配大小,因为可能发生堆栈溢出异常。堆栈溢出异常处理可用于此类调用。在堆栈溢出异常的情况下,可以使用_resetstkoflw()将其恢复。

所以我们使用_alloca的新代码将是:

 int NewFunctionA()
 {
  char* pszLineBuffer = (char*) _alloca(1024*sizeof(char));
  …..
  // Program logic
   ….
 //no need to free szLineBuffer
 return 1;
}
另一答案

编译后,每个具有名称的变量都成为一个解除引用的指针,其地址值的计算方法是向堆栈指针添加(取决于平台,可以“减去”...)“偏移值”(寄存器,包含堆栈实际到达的地址:通常“当前函数返回地址”存储在那里)。

int i,j,k;

(SP-12) ;i
(SP-8) ;j
(SP-4) ;k

为了使这个“总和”有效,偏移必须是常量,以便它们可以直接在指令操作码中编码:

k=i+j;

成为

MOV (SP-12),A;   i-->>A
ADD A,(SP-8) ;   A+=j
MOV A,(SP-4) ;   A-->>k

你在这里看到4,8和12现在是“代码”,而不是“数据”。

这意味着一个接一个的变量需要“other”来保持固定的编译时定义的大小。

动态声明的数组可以是一个例外,但它们只能是函数的最后一个变量。否则,后面的所有变量都将具有一个偏移量,该偏移量必须在该数组分配后的运行时调整。

这就产生了解除引用地址需要算术(不仅仅是普通偏移)的复杂性,或者在声明变量时修改操作码的能力(自修改代码)。

这两种解决方案在性能方面都不是最优的,因为所有这些都可以打破寻址的局部性,或者为每个变量访问添加更多的计算。

另一答案

为什么我们不能在堆栈上分配动态内存(即只在运行时已知的大小的内存)?

您可以使用_alloca()_malloca()的Microsoft编译器。对于gcc来说,这是alloca()

我不确定它是C / C ++标准的一部分,但是alloca()的变体包含在许多编译器中。如果你需要对齐分配,这样一个“n”字节的内存从“m”字节边界开始(其中m是2的幂),你可以分配n + m字节的内存,将m添加到指针并屏蔽掉低位。在十六进制100边界上分配十六进制1000字节内存的示例。您不需要保留_alloca()返回的值,因为它是堆栈内存并在函数退出时自动释放。

char *p;
    p = _alloca(0x1000+0x100);
    (size_t)p = ((size_t)0x100 + (size_t)p) & ~(size_t)0xff;
另一答案

最重要的原因是所使用的存储器可以按任何顺序解除分配,但堆栈需要以固定顺序重新分配存储器,即LIFO顺序。实际上很难实现这一点。

另一答案

阅读一下图灵机,了解为什么事情就像它们一样。一切都围绕着他们作为起点。

https://en.wikipedia.org/wiki/Turing_machine

除此之外的任何东西在技术上都是憎恶和黑客。

以上是关于为什么我们不能在堆栈上分配动态内存?的主要内容,如果未能解决你的问题,请参考以下文章

C ++如何在堆栈上动态分配内存?

删除动态分配的内存

每日一练5

在C语言中,如何给函数分配内存?

堆栈内存是连续的吗?

什么是更有效的堆栈内存或堆? [复制]