-1

我正在使用 malloced 内存和局部变量来查看堆栈和堆如何增长。据我了解,堆向上增长,堆栈向下增长。使用 malloc 分配的所有内存都在堆中分配,局部变量在函数的堆栈中分配。

在以下程序中:

#include <stdio.h>
#include <stdlib.h>
#define SIZE 999999999
void tot(){
    static int c =0;
    int k;
    c++;
    int *pp = malloc(sizeof(int) * SIZE);
    if(pp==NULL){
        fprintf(stderr,"NO memory allocated after call : %d\n",c);
        return;
    }
        printf("%p %d %p\n",&k,c,pp);
        tot();
}

int main(int argc, char *argv[]){
    int k;
    int *pp = malloc(sizeof(int) * 99999);
    if(pp ==NULL){
        fprintf(stderr,"No memory allocated\n");
        return;
    }
        printf("%p %p\n",&k,pp);
        tot();
        return 0;
}

我在函数 tot() 中创建了一个局部变量,它将是 4 个字节和一个 int* 类型的指针变量,这会将每次调用的总堆栈大小增加到略大于 4 个字节。我还使用 malloc 分配了一些内存,并将分配的内存分配给本地指针变量。由于这个 malloced 内存是在堆上分配的,因此堆栈大小应该仍然超过 4 个字节,但根据我在上述程序中的观察,堆栈大小增加了很多。经过大量数学计算后,我发现堆栈帧包括在每个函数调用中创建的分配内存。

虽然删除线

int *pp = (int*)malloc(sizeof(int) * SIZE);

它负责在每个函数调用中分配这么大的内存,将堆栈帧大小减少到 ~4 字节,这非常好。

不过,在这两种情况下,堆栈框架都在向下增长。

为什么我得到这样的输出。我认为在堆上分配动态内存是错误的。为什么 malloced 内存会增加堆栈帧的大小?

编辑:我还尝试通过将指向在一个堆栈帧中分配的内存的指针传递给另一个函数调用(另一个堆栈帧)来访问在另一个堆栈帧中的一个堆栈帧中分配的内存,以便在一个帧中分配的内存可以用于其他调用只是为了验证编译器是否没有在内部将 malloc 转换为 alloca(这可能是大堆栈帧的原因),但这没有任何区别。结果还是一样。

4

2 回答 2

1

C 是一种低级语言,但它仍然对事物进行了足够松散的定义,以至于具体细节可能因平台而异。例如,只要不改变运行程序的结果,编译器就可以自由分配它认为合适的不同数量的堆栈空间(通常是“额外”空间)。

这可以以性能 [1] 的名义或支持某些调用约定来完成。因此,即使一个 int 可能只需要字节空间,编译器也可以在堆栈上分配 16 个字节以保持有利的内存对齐。

如果堆栈大小的“大”增加与 SIZE 相比很小,那么差异可能只是由于这种填充。(请注意,堆栈上的数据不仅仅是局部变量 - 调用函数的返回地址通常会存储在那里,以及编译器决定应该从调用者存储的任何寄存器的内容。)

如果它实际上看起来像是malloc在分配给堆栈(即堆栈在 SIZE 的跳跃中增加),那是令人惊讶的,但代表编译器可能仍然“正确”。根据定义malloc分配内存;不能保证这是从堆中分配的,尽管这肯定是正常的实现。由于 的结果malloc永远不会在调用它的堆栈框架之外访问,因此编译器原则上可以将其优化为alloca调用;但是,再次,我会非常惊讶地看到这一点。

[1] 示例: http: //software.intel.com/en-us/articles/data-alignment-when-migrating-to-64-bit-intel-architecture

于 2013-05-05T16:52:14.310 回答
1

许多 Linux 系统都有ASLR,因此从一次执行到下一次执行的结果malloc可能无法重现;您可以通过禁用 ASLR /proc/sys/kernel/randomize_va_space

实际上,最近的 malloc 实现使用了比or更多的mmap(2)系统调用。所以实际上是由几个不连续的虚拟内存段组成的。通常,稍后调用将已释放区域标记为可重用(但不一定)。sbrkbrkfreemallocmunumap(2)

您可以通过查看 pid 1234 的某些进程的地址空间/proc/1234/maps。阅读proc(5)手册页(并尝试cat /proc/self/maps显示运行该进程的地址空间cat)。

而且您tot的功能是尾递归,大多数GCC编译器会将其优化为循环......(如果您正在编译gcc -O1或更多)。然而,堆栈帧通常需要对齐(例如到 16 个字节)并且还包含前一个帧指针、一些溢出寄存器的空间等......

于 2013-05-05T13:46:38.887 回答