在C语言中,我的变量存储在哪个内存位置?

217

考虑到内存分为数据、堆、栈和代码四个部分,全局变量、静态变量、常量数据类型、函数声明和定义中的局部变量、主函数中的变量、指针以及使用 malloc 和 calloc 动态分配的空间会被存储在哪些部分呢?

我认为它们会被分配如下:

  • 全局变量 -------> 数据段
  • 静态变量 -------> 数据段
  • 常量数据类型 -----> 代码段
  • 函数声明和定义中的局部变量 -------> 栈
  • 主函数中声明和定义的变量 -------> 堆
  • 指针(例如: char *arr, int * arr) -------> 堆
  • 使用 malloc 和 calloc 动态分配的空间 -------> 堆

我仅从 C 的角度来参考这些变量。

如果我有错误之处,请您纠正,因为我对C语言还很陌生。


9
main只是另一个函数。变量在栈上分配,除非像其他地方一样使用了malloc进行动态内存分配。 - simonc
这是否实际由C标准定义?我认为这更依赖于架构。 - m0skit0
1
大部分细节可能取决于实现。(即,没有什么可以禁止一个堆分配所有东西包括“堆栈帧”的C编译器/运行时。) - millimoose
静态变量:https://dev59.com/iXVD5IYBdhLWcg3wGHeu - Ciro Santilli OurBigBook.com
自己的笔记:阅读@Kerrek SB的答案。 - VimNing
9个回答

290

你有一些翻译是正确的,但是出题者至少在一个问题上愚弄了你:

  • 全局变量 -------> 数据 (正确)
  • 静态变量 -------> 数据 (正确)
  • 常量数据类型 -------> 代码和/或数据。当一个常量本身会被存储在数据段中,并且引用它的地方会嵌入到代码中时,需要考虑字符串字面量。
  • 在函数中声明和定义的本地变量 --------> 堆栈 (正确)
  • main函数中声明和定义的变量 -----> 同样是堆栈 (老师想愚弄你)
  • 指针(例如:char *arrint *arr) -------> 取决于上下文是数据还是堆栈。C语言允许你声明全局或static指针,在这种情况下,指针本身将最终位于数据段中。
  • 动态分配空间(使用malloccallocrealloc) --------> 堆栈

值得一提的是,“堆栈”在官方上叫做“自动存储类”。


13
值得一提的是,堆在官方上并没有被正式命名。分配的内存来自某个地方,在标准中没有给那个"某个地方"命名。 - Steve Jessop
8
在一些系统上(特别是Linux和*BSD),还有一个名为alloca的函数,它类似于malloc但是可以在栈上进行内存分配。 - Andreas Grapentin
我猜你明白我的意思,只是为了澄清一下,我不是想问a存储在哪里,而是想问a指向的内存块在哪里。 - myradio
@myradio 在 int a[10]int a[b] 的声明中没有指针,它们声明的是数组。这些数组被放置在自动内存区域中。 - Sergey Kalinichenko

179

对于可能有兴趣了解这些内存段的未来访问者,我正在写关于C语言中5个内存段的重要要点:

一些提示:

  1. 每当执行C程序时,都会在RAM中为程序执行分配一些内存。这些内存用于存储频繁执行的代码(二进制数据)、程序变量等。下面的内存段讲述了同样的内容:
  2. 通常有三种类型的变量:
    • 局部变量(也称为C语言中的自动变量)
    • 全局变量
    • 静态变量
    • 您可以拥有全局静态或局部静态变量,但上述三个是父类型。

C语言中的5个内存段:

1. 代码段

  • 代码段,也称为文本段,是包含频繁执行代码的内存区域。
  • 代码段通常是只读的,以避免由缓冲区溢出等编程错误覆盖。
  • 代码段不包含程序变量,如局部变量(也称为C语言中的自动变量)、全局变量等。
  • 根据C实现,代码段还可以包含只读字符串字面值。例如,当您执行printf("Hello, world")时,字符串“Hello, world”会在代码/文本段中创建。您可以使用Linux操作系统中的size命令验证这一点。
  • 进一步阅读

2. 数据段

数据段分为以下两个部分,通常位于堆区下方或某些实现中位于堆栈上方,但数据段永远不会位于堆和栈区域之间。

3. 未初始化数据段

  • 此段也称为bss
  • 这是包含以下内容的内存部分:
    1. 未初始化的全局变量 (包括指针变量)
    2. 未初始化的常量全局变量
    3. 未初始化的本地静态变量
  • 任何未初始化的全局或静态局部变量都将存储在未初始化数据段中。
  • 例如:全局变量int globalVar; 或静态局部变量static int localStatic; 将保存在未初始化数据段中。
  • 如果声明全局变量并将其初始化为0NULL,则它仍将进入未初始化数据段或bss。
  • 进一步阅读

3. 初始化数据段

  • 此段存储:
    1. 已初始化的全局变量 (包括指针变量)
    2. 已初始化的常量全局变量
    3. 已初始化的本地静态变量
  • 例如:全局变量int globalVar = 1;或静态局部变量static int localStatic = 1; 将保存在已初始化的数据段中。
  • 此段可以进一步分类为初始化只读区和初始化读写区。 初始化的常量全局变量将进入初始化的只读区,而值可在运行时修改的变量将进入初始化的读写区
  • 此段的大小由程序源代码中值的大小确定,在运行时不会更改
  • 进一步阅读

4. 栈段

  • 堆栈段用于存储在函数内创建的变量(函数可以是主函数或用户定义的函数),例如:
    1. 函数的局部变量(包括指针变量)
    2. 传递给函数的参数
    3. 返回地址
  • 存储在栈中的变量将在函数执行完成后立即被删除。
  • 更多阅读

5. 堆段

  • 该段用于支持动态内存分配。如果程序员想要动态分配一些内存,则在C语言中可以使用malloccallocrealloc方法。
  • 例如,当int* ptr = malloc(sizeof(int) * 2)时,将在堆中分配8个字节,并返回该位置的内存地址并将其存储在ptr变量中。 ptr变量将位于栈或数据段中,具体取决于它的声明/使用方式。
  • 更多阅读

在第3行,应该初始化而不是未初始化的数据段。 - Suraj Jain
关于“存储在未初始化数据段中”的问题:您是指“在数据段中未经初始化的存储”吗? - Peter Mortensen
@PeterMortensen 我的意思是两个方面。任何未初始化的全局或静态局部变量都将存储在未初始化数据段中。 - hagrawal7777
如何在C语言中使用全局静态变量? - user3857354
在“一些提示”中,我发现了这个观点:“您可以拥有全局静态或局部静态变量,但以上三种是父类型。”其中您提到了“全局静态”这个术语。我的观点是静态变量不能是全局的。也就是说,如果任何变量必须是全局的,则应该在程序执行完成之前一直可访问。请解释并帮助我纠正错误。 - user3857354
1
现代GNU binutils ld会将.rodata分离出来,并放入自己的只读非执行段中,与代码分开(我在GNU/Linux上进行了测试)。这意味着静态常量,例如字符串文字,不再是Spectre / ROP小工具的潜在候选对象,因为它们位于不可执行的页面中。 - Peter Cordes

13

纠正了你错误的句子

constant data types ----->  code //wrong

本地常量变量 -----> 栈

已初始化的全局常量变量 -----> 数据段

未初始化的全局常量变量 -----> bss

variables declared and defined in main function  ----->  heap //wrong

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

pointers(ex:char *arr,int *arr) ------->  heap //wrong

dynamically allocated space(using malloc,calloc) --------> stack //wrong

指针(例如char *arr,int *arr)------>指向该指针变量的大小将在堆栈中。

考虑你正在动态分配n个字节的内存(使用 malloc calloc ),然后使指针变量指向它。现在,n个字节的内存位于堆中,并且指针变量需要4个字节(如果是64位机器,则是8个字节),这些字节将在堆栈中存储n个字节内存块的起始指针。

注意:指针变量可以指向任何段的内存。

int x = 10;
void func()
{
int a = 0;
int *p = &a: //Now its pointing the memory of stack
int *p2 = &x; //Now its pointing the memory of data segment
chat *name = "ashok" //Now its pointing the constant string literal 
                     //which is actually present in text segment.
char *name2 = malloc(10); //Now its pointing memory in heap
...
}

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


指针可以在堆栈或堆中(特别是:指向指针)。 - argentage
@airza:现在已经更新了。其实我只是在更新那些细节 :) - rashok
1
在下面的内存映射中,您能指出堆栈和堆分别位于哪里吗?我不确定这是否是正确的问题,因为堆栈和内存可能仅适用于运行时。内存映射: "text data bss dec hex filename 7280 1688 1040 10008 2718 a.exe - mahoriR
初始化全局constant变量---->数据段。不,这个答案是错误的,对于旧的链接器来说这个问题是正确的。如果.rodata部分没有与代码一起链接到文本段(Read + eXec)中,就像旧的链接器所做的那样,现代GNU ld默认将其链接到自己的只读且不可执行的段中。非零的全局const变量当然不会放在R+W的.data部分或链接到R+W数据段中,除非完全优化掉了,而零值的变量将放在.bss中。 - Peter Cordes

9

一种流行的桌面架构将进程的虚拟内存分为几个

  • 文本段:包含可执行代码。指令指针取值范围在此范围内。

  • 数据段:包含全局变量(即具有静态链接的对象)。细分为只读数据(如字符串常量)和未初始化数据(“BSS”)。

  • 栈段:包含程序的动态内存,即自由存储区(“堆”)和所有线程的本地栈帧。传统上,C堆和C栈从两端向栈段增长,但我认为这种实践已被放弃,因为它太不安全。

C程序通常将静态存储期的对象放入数据段,动态分配的对象放在自由存储区,而自动对象放在其所属线程的调用栈中。

在其他平台上,例如旧的x86实模式或嵌入式设备上,情况显然可能完全不同。


我相信这种做法已经被放弃了,因为它太不安全了,而且无法实现线程,因为这样需要每个程序有多个堆栈,而且它们不能都在结尾 :-) - Steve Jessop
@SteveJessop:是的,我也这么想。但线程已经存在很长时间了——我不知道所有线程堆栈是否也向后增长,或者它们是否像堆一样向上增长...无论如何,现在所有东西都朝着同一个方向发展,并且有保护页面。 - Kerrek SB

7
从C语言的角度来看,唯一重要的是范围、作用域、链接和访问权限;如何将项目映射到不同的内存段完全取决于个人实现,这会有所不同。语言标准根本不谈论内存段。大多数现代架构的工作方式基本相同;块作用域变量和函数参数将从堆栈分配,文件作用域和静态变量将从数据或代码段分配,动态内存将从堆中分配,某些常量数据将存储在只读段中等等。

4

在存储方面需要记住的一件事是仿佛规则。编译器不要求将变量放置在特定位置,而是可以随意放置它,只要编译后的程序行为仿佛按照抽象C机器的规则在抽象C机器中运行即可。这适用于所有存储持续时间。例如:

  • 一个未被访问的变量可以完全消除——它没有存储...任何地方。 示例 - 看看在生成的汇编代码中有42,但没有404的迹象。
  • 具有自动存储期限并且没有其地址被获取的变量根本不需要存储在内存中。 例如循环变量。
  • const或有效const的变量不需要在内存中。 示例 - 编译器可以证明foo是有效的const并将其用途内联到代码中。bar具有外部链接,编译器无法证明它不会在当前模块之外更改,因此它不会被内联。
  • 使用malloc分配的对象不需要驻留在从堆分配的内存中! 例如 - 注意代码中没有调用malloc,并且值42也从未存储在内存中,而是保留在寄存器中!
  • 因此,使用malloc分配的对象并且引用已丢失而没有使用free释放对象不需要泄漏内存...
  • 使用malloc分配的对象不需要在Unixen上的程序断点(sbrk(0))下的堆内存中...

1
指针(例如:char *arr,int *arr)可以在堆上。不是的,它们可以在堆栈或数据段中。它们可以指向任何地方。

关于 main 和动态分配变量的陈述也是错误的。 - simonc
不仅限于栈或数据段。想象一下指向指针数组的指针。在这种情况下,数组中的指针存储在堆上。 - Sebi2020

0

Linux最小可运行示例及反汇编分析

由于这是一个标准未指定的实现细节,让我们看看编译器在特定实现上的操作。

在本答案中,我将链接到执行分析的具体答案,或直接提供分析,并总结所有结果。

所有这些都在各种Ubuntu / GCC版本中,结果可能在版本之间相当稳定,但如果我们发现任何变化,让我们指定更精确的版本。

函数内部的局部变量

无论是main还是其他任何函数:

void f(void) {
    int my_local_var;
}

如下所示:gdb 中 <value optimized out> 是什么意思?

  • -O0: 堆栈
  • -O3: 如果不溢出,则使用寄存器,否则使用堆栈

有关为什么存在堆栈的动机,请参见:x86 汇编中用于寄存器的 push / pop 指令的功能是什么?

全局变量和 static 函数变量

/* BSS */
int my_global_implicit;
int my_global_implicit_explicit_0 = 0;

/* DATA */
int my_global_implicit_explicit_1 = 1;

void f(void) {
    /* BSS */
    static int my_static_local_var_implicit;
    static int my_static_local_var_explicit_0 = 0;

    /* DATA */
    static int my_static_local_var_explicit_1 = 1;
}
  • 如果初始化为0或未初始化(因此隐式初始化为0): .bss 段,另见: 为什么需要 .bss 段?
  • 否则: .data

char *char c[]

如图所示: C 和 C++ 中静态变量存储在哪里?

void f(void) {
    /* RODATA / TEXT */
    char *a = "abc";

    /* Stack. */
    char b[] = "abc";
    char c[] = {'a', 'b', 'c', '\0'};
}

TODO 足够大的字符串常量也会被放在堆栈上吗?还是使用.data?还是编译失败?

函数参数

void f(int i, int j);

必须遵循相关的调用约定,例如:https://en.wikipedia.org/wiki/X86_calling_conventions 对于X86,它为每个变量指定特定的寄存器或堆栈位置。

然后如What does <value optimized out> mean in gdb?所示,-O0 将所有内容都插入到堆栈中,而-O3 尽可能使用寄存器。

但是,如果函数被内联,则它们被视为常规本地变量。

const

我认为这没有区别,因为您可以取消类型转换。

相反,如果编译器能够确定某些数据从未写入,则理论上即使不是 const 也可以将其放置在 .rodata 中。

TODO 分析。

指针

它们是包含地址(即数字)的变量,因此与其他变量相同 :-)

malloc

这个问题对于malloc来说并没有太多意义,因为malloc是一个函数,在:

int *i = malloc(sizeof(int));

*i是一个包含地址的变量,因此它属于上述情况。

至于malloc内部的工作原理,当您调用它时,Linux内核会将某些地址标记为可写入其内部数据结构,并且当程序最初触及它们时,会发生故障并启用页面表,从而使访问无需segfaul: x86分页如何工作?

请注意,当您尝试运行可执行文件时,这基本上就是exec系统调用在幕后执行的操作:它标记要加载的页面,并将程序写入那里,请参见: Linux下内核如何运行可执行二进制文件?除了exec对加载位置有一些额外限制(例如,代码不可重定位)。

malloc所使用的确切系统调用在现代2020实现中是mmap,而过去则使用了brkmalloc()使用brk()还是mmap()?

动态库

基本上会被mmap到内存中:https://unix.stackexchange.com/questions/226524/what-system-call-is-used-to-load-libraries-in-linux/462710#462710

环境变量和mainargv

以上是初始栈:https://unix.stackexchange.com/questions/75939/where-is-the-environment-string-actual-stored TODO为什么不在.data中?


0
  • 变量/自动变量 ---> 栈区
  • 动态分配的变量 ---> 堆区
  • 已初始化的全局变量 -> 数据段
  • 未初始化的全局变量 -> 数据段 (BSS)
  • 静态变量 -> 数据段
  • 字符串常量 -> 代码段/文本段
  • 函数 -> 代码段/文本段
  • 文本代码 -> 代码段/文本段
  • 寄存器 -> CPU 寄存器
  • 命令行输入 -> 环境变量/命令行参数段
  • 环境变量 -> 环境变量/命令行参数段

环境/命令行部分是什么?它们在Linux中存在吗? - Haoyuan Ge

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接