数组存储在内存中的啥位置?

Posted

技术标签:

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

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

    初始化数据段 BSS 堆栈 堆 代码

现在考虑以下程序:

#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 将存储在数据部分的程序映像中。在运行时,如果您的程序甚至创建了数组变量(因为正如其他人所说,优化器可以删除任何无效的内容),它将在堆栈上为数组创建空间,然后将初始化程序复制到其中。或者,如果使用优化器,它可能会删除初始化程序并将其替换为内联寄存器集指令。 【参考方案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)-------->栈堆

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

【讨论】:

以上是关于数组存储在内存中的啥位置?的主要内容,如果未能解决你的问题,请参考以下文章

方法存储在内存中的啥位置?

React Native - AsyncStorage 和状态存储在硬件内存中的啥位置?

静态成员存储在内存中的啥位置? C#.Net中的堆栈/堆[重复]

常量变量存储在 C 中的啥位置?

对象的实例变量存储在 JVM 中的啥位置?

会话存储在 Rails 中的啥位置?