11

我了解到,全局变量的内存是在程序启动时分配的,而局部变量的内存是在进行函数调用时分配的。

案例 1:
我声明了一个大小为 63500000 的全局整数数组,使用的内存为 256 MB
Ideone Link

include <stdio.h>
int a[63500000];
int main()
{
    printf ("This code requires about 250 MB memory\n");
    return 0;
}

案例 2:
我在 main() 中声明了一个相同大小的本地整数数组,使用的内存为 1.6 MB
Ideone 链接

#include <stdio.h>
int main()
{
    int a[63500000]= {1,5,0};
    printf ("This code requires only 1.6 MB \n");
    //printf ("%d\n", a[0]);
    return 0;
}

案例 3:
我在另一个函数中声明了一个相同大小的本地整数数组,使用的内存为 1.6 MB
Ideone Link

#include <stdio.h>
void f()
{
    int a[63500000];
}

int main()
{
    f();
    return 0;
}

请解释为什么使用的内存有所不同或我的内存分配概念是错误的??

4

3 回答 3

25

首先:ideone编译器是GCC。

那么,当你编译这个时,GCC 会做什么呢?:

void foo ()
{
  int a[63500000];
}

gcc -S -O2 foo.c生成:

foo:
    pushl   %ebp
    movl    %esp, %ebp
    popl    %ebp
    ret

即,根本没有在堆栈上分配任何内容。

该数组被 GCC 简单地优化掉了,因为它从未被使用过。

GCC 不会对全局执行此操作,因为可能在另一个编译单元中使用了全局,因此不确定它是否从未使用过。另外:全局不在堆栈上(因为它是全局的)。

现在,让我们看看当你实际使用本地数组时会发生什么:

int bar (int a, int b, int c)
{
  int f[63500000];
  f[a] = 9;
  f[b] = 7;
  return f[c];
}

情况非常不同:

bar:
    pushl   %ebp
    movl    %esp, %ebp
    subl    $254000000, %esp
    movl    8(%ebp), %eax
    movl    $9, -254000000(%ebp,%eax,4)
    movl    12(%ebp), %eax
    movl    $7, -254000000(%ebp,%eax,4)
    movl    16(%ebp), %eax
    movl    -254000000(%ebp,%eax,4), %eax
    leave
    ret

这一行:subl $254000000, %esp对应于数组的大小。即内存在堆栈上分配的。

现在,如果我尝试bar在程序中使用该函数会怎样:

int bar (int a, int b, int c)
{
  int f[63500000];
  f[a] = 9;
  f[b] = 7;
  return f[c];
}

int main (void)
{
  return bar (0, 0, 0);
}

我们已经看到,该bar函数在堆栈上分配了大约 250 兆字节。在我的默认 GNU/Linux 安装中,堆栈大小限制为 8MB。因此,当程序运行时,会导致“分段错误”。如果需要,我可以通过在 shell 中执行以下命令来增加它:

ulimit -s 1000000 #i.e. allow stack size to grow close to 1GB

然后我可以运行程序,它确实会运行。

它在 ideone 网站上失败的原因是他们在执行程序时限制了堆栈大小(他们应该这样做,否则恶意用户可能会弄​​乱他们的系统)。

于 2012-08-15T21:34:58.490 回答
8

案例 2、3

您在函数内部定义的变量在堆栈上分配。这意味着当函数退出时,相关的内存被清理(堆栈被“弹出”)。

情况1

在全局范围内定义的变量分配在一个数据段(或者,通常是从操作系统请求的内存空间)中,该数据段存在于进程的生命周期中。

此外

使用malloc分配的内存是从堆中分配的,并且在使用free显式释放之前一直保持分配状态。

请注意,现代操作系统可能会提供程序请求的地址空间,但不会在物理上用 RAM 支持该地址空间,直到内存(或通常称为页面的内存的一部分)被物理访问。

于 2012-08-15T21:12:17.630 回答
2

case 2并且case 3会导致堆栈溢出,因为您要求64 MB的堆栈内存,其中您的堆栈在 Linux 上通常为8 MB 。这将导致随机的坏事和/或核心转储和崩溃。

这个答案极大地解释了进程地址空间(.text、.bss、.data)的各个部分以及如何完成变量的各种分配。

于 2012-08-15T21:23:43.007 回答