栈变量与堆变量

48

我是否正确地认为:

char *buff[500];

...创建了一个栈变量,并且:

char *buff = (char *)malloc(500);

...创建一个堆变量吗?

如果是这样,何时以及为什么会使用堆变量而不是栈变量,反之亦然。我知道栈更快,还有其他原因吗。

最后一个问题,主函数是否是堆栈上的堆栈帧?


6
为了让这两个分配方式可比较,您可能希望第一个版本没有 *,对吗? - Jens Gustedt
3
一个问题:没有所谓的“堆变量”。在你的例子中,char *buff是一个变量,但它指向的内存(通过malloc获得)不被称为变量;它被称为对象。在C语言中,变量具有名称、作用域、存储期和可能的链接性。每个变量都有一个关联的对象,其中包含(表示)它的值,但反之不成立。 - R.. GitHub STOP HELPING ICE
8个回答

69

第一个创建了一个字符指针数组在栈中,大约 500*4字节,第二个则在堆中分配了500个字符,并将栈的字符指针指向它们。

在栈中分配内存容易快速,但是栈的空间有限,而堆虽然较慢但更大。此外,栈分配的值在离开作用域后会被"删除",因此非常适合小的本地值,如原始变量。

如果您在栈中分配太多内存,可能会耗尽栈并崩溃,main及其执行的所有函数都在栈中具有堆栈帧,并且所有函数的本地变量都存储在其中,因此过度调用函数可能会导致堆栈溢出。

通常的经验法则是将使用频繁且大于100字节的任何内容分配到堆中,将小变量和指针分配到栈中。


太棒了的回复!谢谢Arkaitz。 - Jack Harvin
3
在使用堆内存时,需要格外小心。由于指针存储在栈上(因此是局部的),这会导致内存管理问题,如悬空指针。因此最好使用智能指针。 - letsc

11

看到你写了

char *buff = (char *)malloc(500);

你可能是想表达

char buff[500];    instead of 
char* buff[500];

在你的第一个例子中(因此你有一个字符数组,而不是指向字符的指针数组)

是的,“堆栈”上的分配速度更快,因为您只需增加存储在ESP寄存器中的指针。

如果您想要:

1)比堆栈可容纳的内存更大的内存(通常更早)

2)将由被调用函数分配的内存传递给调用函数,则需要堆变量。


是的,抱歉,忘记加上指针了。又是一个完美的答案。天啊,希望我能选出2个最佳答案。 - Jack Harvin
@eznme:不好意思问个蠢问题,但是……我想知道:2)将由被调用函数分配的内存传递给调用函数。比如说,我通过简单地说“vector<int> someReturnedVector = myFunctionReturningVectorOfInts();”来分配一个向量。我认为由“myFunctionReturningVectorOfInts()”函数创建的向量是在堆栈上创建的,然后返回到调用它的函数,或者我完全误导了吗? - AnyOneElse
@AnyOneElse 这个问题和这些答案是关于C而不是C++的。C++有更多关于通过值、指针、引用或移动传递的规则。我建议你开一个新的问题来确定。 - Bernd Elkemann

9

你的buff们不是等价的。

第一个 (char *buff[500]) 是一个包含500个指针的数组;第二个 (char *buff = (char *)malloc(500)) 是一个指针。

这个指针 (在堆栈上) 指向堆上的500个字节内存(如果malloc调用成功)。
指针数组位于堆栈上,它的指针没有被初始化。


4

除非使用C99,否则在使用栈时,数组的大小必须在编译时就已知。这意味着你不能这样做:

int size = 3; // somewhere, possibly from user input
char *buff[size];

但是使用“堆”(动态分配),您可以提供任何尺寸。这是因为内存分配是在运行时执行的,而不是硬编码到可执行文件中。


1
在一些系统(Linux、BSD)中,有一个名为alloca(...)的函数,它可以在自动存储区中分配内存,其效果与C99变长数组基本相同。我使用“自动”这个词,因为C语言没有定义栈和堆的概念。C语言将它们称为自动存储区和分配存储区。此外,C99标准中还有静态存储区(6.2.4)。 - datenwolf
@datenwolf:很高兴听到这个消息。在C++(我偏爱的语言)中,我非常挑剔使用不准确的术语“堆栈”和“堆”,但是我不想在这里深入讨论,因为我不知道在C语境下我的抱怨有多准确。 :) - Lightness Races in Orbit
很棒,有人指出了在C99中使用VLA(可变长度数组)的可能性,即使这个术语并没有被使用。我看到许多其他答案忘记了这个可能性。在我看来,对于那些刚开始学习C语言的人来说,这是有用的信息。 - rsc
VLA和alloca都非常危险,几乎从不有用(因为如果您以一种可以确保安全的方式使用它们,则最好使用一个大的固定大小缓冲区或'malloc')。 - R.. GitHub STOP HELPING ICE

3

C标准中既没有“堆”也没有“栈”这两个词。相反,我们有四种存储期(其中两种是自动分配的):

  • char buff[500]; // note the missing * to match the malloc example
    

    within a function declares the object buff as an array of char and having automatic storage duration. The object will cease to be when the block where the object was declared, is exited.

  • char *buff = malloc(500);  // no cast necessary; this is C
    

    will declare buff as a pointer to char. malloc will reserve 500 continuous bytes and return a pointer to it. The returned 500-byte object will exist until it is explicitly freed with a call to free. The object is said to have allocated storage duration.


这就是C标准的全部规定。它没有指定char buff[500]需要从“堆栈”分配,或者需要存在堆栈。它也没有指定malloc需要使用某种“堆”。相反,编译器可能会像下面这样内部实现char buff[500]:

{
    char *buff = malloc(500);
    free(buff);
}

它可以推断出分配的内存未被使用,或仅被使用一次,因此使用基于堆栈的分配而不是实际调用 malloc

在实践中,大多数当前的编译器和环境使用一种称为堆栈的内存布局来处理自动变量,而具有已分配存储期的对象则被称为来自“堆”,这是与有序堆栈相比的混乱无序的比喻,但它不一定是必须的。


1

这确实是在堆栈上分配的变量:

char buff[500]; // You had a typo here?

这是在堆上的:

char *buff = (char *)malloc(500);

你为什么要使用其中一个而不是另一个?

  • char *buff[500]中,500需要是编译时常量。如果500在运行时计算,则无法使用它。
  • 另一方面,堆栈分配是瞬间完成的,而堆分配需要时间(因此会产生运行时性能成本)。
  • 堆栈上的空间受线程堆栈大小的限制(通常为1MB,在出现堆栈溢出之前),而堆上有更多可用空间。
  • 如果您在堆栈上分配了一个足够大的数组以占用由操作系统管理的2页以上虚拟内存,并在执行任何其他操作之前访问数组的末尾,则可能会发生保护错误(这取决于操作系统)

最后:每个被调用的函数都有一个堆栈帧。 main函数也不例外。它甚至不比程序中的其他函数更特殊,因为当程序开始运行时,第一段运行的代码位于C运行时环境内。运行时准备好开始执行您自己的代码后,它会像调用任何其他函数一样调用main


如果500是在运行时计算的,您可以使用它,但这是很危险的,并且没有办法知道哪些值是安全的... - R.. GitHub STOP HELPING ICE
动态数组大小的使用取决于编译器。我记得当我看到以下代码时感到很困惑:char buff [myDynamicLength]。Visual Studio不允许我编译它,但Dev++可以(我猜是使用GCC)。但无论如何,这似乎是一种不好的做法,应该避免使用。 - JustAMartin

1

堆变量可以动态创建,即您可以向用户请求一个大小并使用此大小分配新变量。

栈变量的大小必须在编译时知道。

正如您所说,栈变量分配和访问速度更快。因此,我建议每次在编译时知道大小时都使用它们。否则,您别无选择,必须使用malloc()


3
不,自C99起,C语言支持在堆栈中生存的可变长度数组。 - Jens Gustedt

0

这两者并不等价。第一个是一个大小为500的数组(在堆栈上), 其中包含指向字符的指针。第二个是一个指向大小为500的内存块的指针,可以使用索引运算符。

char buff[500];

char *buff = (char *)malloc(sizeof(char)*500);

应优先使用堆栈变量,因为它们不需要释放。堆变量允许在作用域之间传递数据以及进行动态分配。


应优先使用堆栈变量,但不应过度使用大型变量。 - Mawg says reinstate Monica

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接