堆栈中的内存分配

32

这可能看起来是一个非常基本的问题,但一直在我的脑海中:

当我们分配一个局部变量时,它会进入堆栈。同样,动态分配会导致变量进入堆。现在,我的问题是,这个变量实际上是否位于堆栈或堆上,还是我们只是在堆栈和堆中有一个引用。

例如,假设我声明一个变量int i。现在这个i被分配在堆栈上。因此,当我打印i的地址时,这将是堆栈上的一个位置吗?对于使用堆的情况也是同样的问题。

6个回答

91

我不完全确定你在问什么,但我会尽力回答。

以下代码在堆栈中声明了一个变量i

int i;

当我使用&i请求地址时,我得到的是堆栈上实际位置。

当我使用malloc动态分配内存时,实际上存储了两个数据片段。动态内存在堆上分配,而指针本身在栈上分配。所以在这段代码中:

int* j = malloc(sizeof(int));

这是为一个整数在堆上分配空间。它也为指针(j)在栈上分配了空间。变量 j 的值设置为由 malloc 返回的地址。


感谢Chris的回答。这正是我在寻找的答案。因此,我们会遇到程序堆栈溢出的问题,但永远不会遇到堆溢出的问题,因为堆受到内存系统的限制。 - Samir Baid
2
实际上,程序很快就用尽堆栈空间的唯一原因是,将堆栈空间限制得非常小是一种常见做法(我认为8KB相当普遍)。而且,如果允许,堆可能会变得相当大。 - Chris Eberle
1
@Samir 不是的。堆栈和堆都受系统内存量的限制。程序在耗尽堆之前就会耗尽堆栈,因为堆栈大小通常比堆小几个数量级。然而,程序仍然可以耗尽堆。 - Matt Ball
1
@Chris:在Windows上,限制通常为1MB,而不是8kB。我认为其他系统也有类似的限制。当然,对于嵌入式系统来说,这可能会非常不同。 - Rudy Velthuis
1
@Rudy:我认为在Windows上限制是编译进二进制文件的,因此取决于开发者。我肯定可以相信1MB是默认值,如果你问我,8KB似乎相当苦行... - Chris Eberle
显示剩余2条评论

18

希望以下内容对您有所帮助:

void foo()
{
    // an integer stored on the stack
    int a_stack_integer; 

    // a pointer to integer data, the pointer itself is stored on the stack
    int *a_stack_pointer; 

    // make a_stack_pointer "point" to integer data that's allocated on the heap
    a_stack_pointer = (int*)malloc(10 * sizeof(int));
}

对于堆栈变量,变量本身(实际数据)存储在堆栈上。

对于在堆上分配的内存,底层数据总是存储在堆中。指向此内存/数据的指针可能存储在堆栈上。

希望这可以帮到你。


这个回答很有帮助,但是你能给我解释一个场景吗?在堆分配的内存中,指针可能不会存储在栈上的情况。 - Samir Baid
@Samir:你可能有一个更复杂的数据结构,其中堆分配的数据包含指向其他堆分配数据段的指针。传统的链表实现就是一个例子,其中列表中的每个“节点”都包含指向下一个“节点”的指针,依此类推。 - Darren Engwirda

7
指针变量本身将驻留在堆栈中。指针指向的内存将驻留在堆中。
int *i = malloc(sizeof(int));

i会驻留在堆栈上,而*i所指向的实际内存则在堆上。


4

我同意Chris的看法。这是另一种解释方法。请考虑以下代码:

int* j = malloc(sizeof(int));
free(j);

即使使用 free(j) 释放堆内存,指针仍然存在,我们需要明确将其设为 NULL。这明显表明指针还有一个栈对应变量,否则在释放命令之后它本该不存在。这个栈变量是指向堆上使用 malloc 动态分配的内存地址的指针。

2
伊伯勒先生的回答是100%正确的,但由于谷歌在搜索“malloc heap or stack”时将此显示为第一个答案,因此我必须补充说明,malloc()大多数情况下会在堆上分配数据。如果分配的数据大于MMAP_THRESHOLD,通常在32位系统上为128kb,则malloc()不会使用堆,而是在匿名内存段中分配数据,该内存段通常位于堆栈下方,向低内存增长。
这是动态加载库所在的同一区域(libc.so等)。以下是man malloc中相关的段落:
通常,malloc()从堆中分配内存,并根据需要使用sbrk(2)调整堆的大小。当分配大于MMAP_THRESHOLD字节的内存块时,glibc malloc()实现将内存作为私有匿名映射使用mmap(2)分配。默认情况下,MMAP_THRESHOLD为128 kB,但可使用mallopt(3)进行调整。在Linux 4.7之前,使用mmap(2)执行的分配不受RLIMIT_DATA资源限制的影响;自Linux 4.7以来,此限制也适用于使用mmap(2)执行的分配。
作为一个实际的例子,请随时查看以下帖子。它基本上使用malloc()分配300kb,然后运行pmap 来显示相关的内存段。

1
相信 MMAP_THRESHOLD 不是 ANSI/ISO C 或任何 POSIX 标准的一部分。仍然很有趣,但并不是所有 C 实现的固有真理。不过看起来对于 glibc 和 musl 是正确的。 - Wyatt Ward

0

栈或堆不是独立的内存,它们是系统分配给运行程序的内存段,只是在内存中组织数据的不同方式。

因此,当你得到 &i 时,它就是一个内存地址,就这么简单。


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