【问题标题】:Where is an array stored in memory?数组存储在内存中的什么位置?
【发布时间】:2021-01-17 19:46:18
【问题描述】:

我试图了解在 C 程序中如何管理内存。我知道内存中有以下几个段:

  1. 初始化数据段
  2. BSS
  3. 堆栈
  4. 代码

现在考虑以下程序:

#include <stdio.h>

int main(){
    int arr[4] = {1,2,3,4};
    int x = 10;

    printf("Hello World!");
}

在上面的程序中,arr 和 x 都是在 main 函数中本地声明的。我认为这意味着它们都将在函数堆栈上分配空间。 但是,当我在 linux 上运行 size 命令时,我发现数组实际上是在数据段中分配空间的。

我在网上搜索过这个,但发现了相互矛盾的信息。一些答案说所有本地声明的变量都应该进入堆栈,而另一些答案则说数组应该进入堆。如果我使用 malloc 动态分配内存,我认为数组会进入堆,但在本例中并非如此。

【问题讨论】:

  • 请注意,您的初始化程序 {1,2,3,4} 将存储在数据部分的程序映像中。在运行时,如果您的程序甚至创建了数组变量(因为正如其他人所说,优化器可以删除任何无效的内容),它将在堆栈上为数组创建空间,然后将初始化程序复制到其中。或者,如果使用优化器,它可能会删除初始化程序并将其替换为内联寄存器集指令。

标签: c memory-management process


【解决方案1】:

我已经在网上搜索过,但发现了相互矛盾的信息。

请不要随意阅读博客之类的,它们通常包含不良信息。在 Stack Overflow 上,错误信息往往会被否决,或者至少通常会让 cmets 指出不准确和谬误。

在上面的程序中,arr 和 x 都是在 main 函数中本地声明的。我认为这意味着它们都将在函数堆栈上分配空间。

C 标准没有指定应该如何为变量对象分配内存。它仅指定对象具有存储持续时间,它定义了变量对象

的生命周期
  • 静态,从程序开始到最后都有生命周期
  • 自动,它将具有包含声明(或复合文字)的最内层块 { ... } 的生命周期,直到块结束
  • thread-local,具有线程的生命周期
  • 分配的对象,从malloc/calloc/realloc/aligned_alloc 直到对应的free/realloc 都处于活动状态。

除此之外,C 标准规定对象在其生命周期内将

  • 为其保留内存
  • 并且有一个常量地址(您可以使用&amp; 运算符观察)

现在,除此之外,还有一条the so-called as-if 规则说只要程序的外部行为相同,编译器就可以生成任何程序代码,外部行为意味着输入、输出、访问 volatile对象等等。

你程序中的变量有自动存储期限,这意味着每次你进入main函数你都会有新的个对象新的生命周期 直到 main 函数结束。通常这意味着它们将被存储在 stack 中,因为它将以最小的开销很好地处理分配和释放。但是您的程序具有与

相同的外部行为
#include <stdio.h>

int main(void) {
    printf("Hello World!");
}

表示编译器可以完全消除这两个变量,不为它保留任何空间。

现在,如果您打印变量的地址

#include <stdio.h>

int main(void) {
    int arr[4] = {1,2,3,4};
    int x = 10;

    printf("Hello World! %p, %p\n", (void *)arr, (void *)&x);
}

因为变量有它们的地址并用于输出,C 不能优化它们。他们现在在堆栈上吗?好吧,C标准不说了。它们至少需要从 main 开始到结束的生命周期 - 但 C 编译器可以决定为它们使用堆栈,因为该程序的外部行为与

#include <stdio.h>

static int arr[4] = {1,2,3,4};
static int x = 10;
    
int main(void) {
    printf("Hello World! %p, %p\n", (void *)arr, (void *)&x);
}

这会将这些变量放在静态数据段中;当然地址会有所不同,但 C 也不保证特定对象在内存中的位置,只是它们会有地址。

【讨论】:

    【解决方案2】:

    但是,当我在 linux 上运行 size 命令时,我发现数组实际上是在数据段中分配空间。

    我认为你误解了你所看到的。

    C 标准对此没有任何说明。它只说arr 具有自动存储期限。但是,大多数(如果不是全部)系统会将xarr 保存在堆栈中。

    试试这个代码:

    #include<stdio.h>
    
    int main(){
        int arr[4] = {1,2,3,4};
        int x = 10;
        static int i = 0;
    
        printf("Hello World! arr is here %p and x is here %p\n", (void*)arr, (void*)&x);
        ++i;
        if (i < 3) main();
        
        return 0;
    }
    

    可能的输出:

    Hello World! arr is here 0x7ffcdaba4170 and x is here 0x7ffcdaba416c
    Hello World! arr is here 0x7ffcdaba4140 and x is here 0x7ffcdaba413c
    Hello World! arr is here 0x7ffcdaba4110 and x is here 0x7ffcdaba410c
    

    即使这不是一个可靠的证据,它也强烈表明系统正在使用一个堆栈并且堆栈向低地址增长并且arrx 都存储在该堆栈上。

    顺便说一句:打印堆栈指针不能以可移植的方式完成,但这是一个很好的阅读:Print out value of stack pointer

    【讨论】:

    • 我明白你的意思。让我感到困惑的是,当我使用 size a.out 命令查看内存分配时。如果我不包含数组并且只有一个var,例如int i for ex,那么内存有bss 8和数据600。但是当我将arr包含到程序中时,数据段变成了608。现在,我试过了添加更多变量,例如 int a、b、c 并给它们一些值(或者它们可能最终在 BSS 中!)但数据段没有改变!它仍然是 608,如果我删除阵列,它会再次变为 600!所以这让我很困惑。
    • @JANVISHARMA 您可能忘记了本地数组的初始化值(即{1,2,3,4})也可能存储在程序中。顺便提一句。我只是在Compiler Explorer 上摆弄你的例子。在那里你可以看到在sub rsp, 40 中考虑了本地数组的空间(向下移动堆栈指针以为本地变量分配空间)但是初始化只是用movs 完成 - 它没有常量数据(虽然有比如我的printf()s 的常量字符串。
    • @JANVISHARMA 而且,如果您喜欢使用我的链接示例,请尝试将编译器选项从 -O2 更改为 -O0... ;-)
    【解决方案3】:

    C 中程序的存储工作如下:

    全局变量------->数据

    静态变量------->数据

    常量数据类型 -----> 代码和/或数据。当常量本身存储在数据段中并且对它的引用将嵌入到代码中时,考虑使用字符串文字

    局部变量(在函数中声明和定义)-------->栈

    在主函数中声明和定义的变量----->堆也是栈

    pointers(ex: char *arr, int *arr) -------> 堆数据或堆栈,取决于上下文。 C 允许您声明一个全局或静态指针,在这种情况下,指针本身将在数据段中结束。

    动态分配的空间(使用malloc、calloc、realloc)-------->栈堆

    值得一提的是,“栈”被官方称为“自动存储类”。

    【讨论】:

      猜你喜欢
      • 2019-08-13
      • 2013-04-24
      • 2021-10-17
      • 2010-11-20
      • 1970-01-01
      • 2011-02-21
      • 2020-02-07
      • 1970-01-01
      • 2015-06-13
      相关资源
      最近更新 更多