局部变量内存和malloced内存位置

时间:2013-05-05 13:38:11

标签: c linux gcc stack malloc

我正在使用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个字节,但根据我在上面程序中的观察,堆栈大小增加了很多。经过大量的数学运算后,我发现堆栈帧包含了在每个函数调用中创建的malloced内存。

虽然删除了行

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

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

但在两种情况下,堆栈框架都在向下发展。

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

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

2 个答案:

答案 0 :(得分:1)

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

实际上,最近的malloc实现使用的mmap(2)系统调用比sbrkbrk更多。因此,heap实际上是由几个非连续的virtual memory段组成的。通常,稍后调用free会将释放的区域标记为malloc可重复使用(但不一定munumap(2))。

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

tot函数为tail recursive,大多数GCC编译器会将其优化为循环....(如果您使用gcc -O1或更多编译)。但是,堆栈帧通常需要对齐(例如16个字节),并且还包含前一个帧指针,一些空间来溢出寄存器等等。

答案 1 :(得分:1)

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

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

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

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

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