【问题标题】:Memory allocation for global and local variables全局和局部变量的内存分配
【发布时间】:2012-08-12 05:18:39
【问题描述】:

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

案例 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 link

#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;
}

请解释为什么使用的内存有差异或者我的内存分配概念是错误的??

【问题讨论】:

  • 您知道,您应该直接在答案中发布代码,而不是提供 Ideone 链接
  • 您如何知道这是程序消耗的内存量?
  • 在您的本地数组示例中,您实际上并未使用大部分数组,因此编译器可以安全地对其进行优化。
  • @ArjunShankar 先生,每当我们在 ideone 中编译程序时,它都会显示程序所需的时间和使用的内存。
  • @nos - 谢谢。我现在明白了。

标签: c memory-management


【解决方案1】:

案例 2、3

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

案例 1

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

另外

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

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

【讨论】:

  • 如果我在 case 2 和 case 3 发生分段错误时尝试执行 memset(a, 0, sizeof(a))。为什么?
  • 关于情况 2 和 3,堆栈上根本没有分配内存。曾经。它被 GCC 简单地优化掉了。您可以在这里看到原因:ideone.com/1a7A1 即 OP 的 250MB 数组示例和这个 4 字节示例,都使用几乎相同数量的内存,正如 ideone 报告的那样。
【解决方案2】:

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

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

【讨论】:

    【解决方案3】:

    首先: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 网站失败的原因是他们在执行程序时限制了堆栈大小(他们应该这样做,否则恶意用户可能会弄​​乱他们的系统)。

    【讨论】:

    • 如果我在 case 2 和 case 3 中尝试做 memset (a, 0, sizeof(a)),为什么会抛出 SIGSEV 异常?
    • @Snehasish - 看看我回答的最后一点。我已经编辑过了。这是因为堆栈大小限制(这是操作系统设置的限制)
    • +1 用于解释优化后的版本,不会导致堆栈溢出。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-08-02
    • 2016-05-16
    • 1970-01-01
    • 1970-01-01
    • 2023-03-19
    • 2021-12-09
    • 1970-01-01
    相关资源
    最近更新 更多