【问题标题】:Where in memory are my variables stored in C?我的变量在 C 中存储在内存的哪个位置?
【发布时间】:2015-08-25 11:20:38
【问题描述】:

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

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

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

我只是从 C 的角度来指代这些变量。

如果我是 C 新手,如果我错了,请纠正我。

【问题讨论】:

  • main 只是另一个函数。除非malloc 像其他地方一样,否则变量会进入堆栈。
  • 这实际上是由C标准定义的吗?我认为这更依赖于架构。
  • 大部分细节可能依赖于实现。 (即,没有什么可以禁止 C 编译器/运行时堆分配所有内容,包括“堆栈帧”。)
  • 我自己的注意事项:阅读@Kerrek SB 的答案。

标签: c memory memory-management types


【解决方案1】:

你答对了其中一些,但写这些问题的人至少在一个问题上欺骗了你:

  • 全局变量------->数据(正确)
  • 静态变量------->数据(正确)
  • 常量数据类型----->代码和/或数据。当常量本身存储在数据段中并且对它的引用将嵌入到代码中时,请考虑使用字符串文字
  • 局部变量(在函数中声明和定义)-------->堆栈(正确)
  • main函数中声明和定义的变量-----> heap也栈(老师想骗你)
  • pointers(ex: char *arr, int *arr) -------> heap 数据或堆栈,取决于上下文。 C 允许您声明一个全局或static 指针,在这种情况下,指针本身最终会出现在数据段中。
  • 动态分配空间(使用malloc,calloc,realloc) --------> stack heap

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

【讨论】:

  • 另外值得一提的是,堆官方根本不叫任何东西。分配的内存来自某处,标准中没有那个“某处”的名称。
  • 在某些系统上,(即Linux和*BSD)还有alloca,其工作方式类似于malloc,但会进行堆栈分配。
  • 我想你明白我的意思,只是为了说清楚,我不是要问a 存储在哪里,而是要问a 指向的内存块在哪里。
  • @myradio int a[10]int a[b] 声明中没有指针,它们声明了数组。这些数组被放置在自动存储区中。
【解决方案2】:

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

请注意:

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

C 中的 5 个内存段:

1。代码段

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

数据段

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

2。未初始化的数据段

  • 此段也称为 bss
  • 这是包含以下内容的内存部分:
    1. 未初始化的全局变量 (包括指针变量)
    2. 未初始化的常量全局变量
    3. 未初始化的局部静态变量
  • 任何未初始化的全局或静态局部变量都将存储在未初始化的数据段中
  • 例如:全局变量int globalVar;或静态局部变量static int localStatic;将存储在未初始化的数据段中。
  • 如果您声明一个全局变量并将其初始化为0NULL,那么它仍然会转到未初始化的数据段或bss。
  • Further reading

3。初始化数据段

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

4。堆栈段

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

5。堆段

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

【讨论】:

  • 在3.初始化数据段中不应该是初始化而不是未初始化。
  • 重新“存储在未初始化的数据段中”(多实例):你的意思是“存储在未初始化的数据段中”吗?跨度>
  • @PeterMortensen 我的意思是两件事。 “任何未初始化的全局或静态局部变量都将存储在未初始化的数据段中”
  • 我们如何在 C 中拥有全局静态变量?
  • 现代 GNU binutils ld.rodata 分开,将其放在自己的只读 non-exec 段中,与代码分开(我在 GNU/Linux 上测试过) .这意味着像字符串文字这样的静态常量不再可能是 Spectre / ROP 小工具的候选对象,因为它们位于不可执行的页面中。
【解决方案3】:

纠正你的错误句子

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

pointers(ex:char *arr,int *arr) -------> 该指针变量的大小将在堆栈中。

假设您正在动态分配 n 字节的内存(使用 malloccalloc),然后使指针变量指向它。现在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)-------->堆

【讨论】:

  • 指针可以在堆栈或堆中(特别是:指向指针的指针)
  • @airza :现已更新。实际上我只是在更新这些细节:)
  • 在下面的内存图中,你能指出堆栈和堆在哪里吗?我不确定这是否是正确的问题,因为堆栈和内存可能仅在运行时适用。内存映射:“文本数据 bss dec hex 文件名 7280 1688 1040 10008 2718 a.exe”
  • 初始化全局 constant 变量 -----> 数据段 不,这个答案是错误的,这个问题对于旧链接器来说是正确的.如果.rodata 部分没有像旧链接器那样与代码一起链接到文本段(Read + eXec),现代GNU ld 默认将其链接到它自己的只读段中,不是 可执行文件。如果没有完全优化,非零全局 const 变量肯定不会进入 R+W .data 部分或链接到 R+W 数据段。你是对的,零值的会进入.bss
【解决方案4】:

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

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

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

  • 堆栈段:包含程序的动态内存,即所有线程的空闲存储(“堆”)和本地堆栈帧。传统上C栈和C堆过去都是从相反的两端长进栈段,但我相信这种做法已经被放弃了,因为太不安全了。

C 程序通常将具有静态存储持续时间的对象放入数据段,将动态分配的对象放入空闲存储区,并将自动对象放入其所在线程的调用堆栈中。

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

【讨论】:

  • “我相信这种做法已经被放弃了,因为它太不安全了” - 并且无法实现线程,因为从那时起每个程序需要多个堆栈并且它们不能都在结束:-)
  • @SteveJessop:是的,我也是这么想的。但是线程已经存在很长时间了——我不知道是不是所有的线程堆栈也都向后增长了,或者它们是否会像堆一样长大......无论如何,现在一切都朝着同一个方向发展并且有守卫页面。
【解决方案5】:

我只是从 C 的角度来指代这些变量。

C 语言 的角度来看,重要的是范围、范围、链接和访问;究竟如何将项目映射到不同的内存段取决于各个实现,并且会有所不同。语言标准根本没有谈论内存段。大多数现代架构的行为方式大致相同。块范围变量和函数参数将从堆栈分配,文件范围和静态变量将从数据或代码段分配,动态内存将从堆分配,一些常量数据将存储在只读段中等。

【讨论】:

    【解决方案6】:

    关于存储需要记住的一件事是as-if rule。根据抽象 C 机器的规则。这适用于所有存储持续时间。例如:

    • 可以完全消除一个未被访问的变量 - 它没有存储......任何地方。 Example - 看看生成的汇编代码中有42,但没有404 的迹象。
    • 具有自动存储持续时间且未获取其地址的变量根本不需要存储在内存中。 An example would be a loop variable.
    • const 或有效const 的变量不需要在内存中。 Example - 编译器可以证明 foo 是有效的 const 并将其使用内联到代码中。 bar 具有外部链接,编译器无法证明它不会在当前模块之外更改,因此它没有被内联。
    • malloc 分配的对象不需要驻留在从堆分配的内存中! Example - 注意代码没有调用 malloc 并且值 42 也没有存储在内存中,它保存在寄存器中!
    • 因此,一个已由malloc 分配的对象在没有用free 解除分配对象的情况下丢失了引用不需要泄漏内存...
    • malloc 分配的对象不必在 Unixen 上的程序中断 (sbrk(0)) 的堆内下方...

    【讨论】:

      【解决方案7】:

      指针(ex:char *arr,int *arr) -------> 堆

      不,它们可以在堆栈上或数据段中。他们可以指向任何地方。

      【讨论】:

      • 关于main和动态分配变量的说法也是错误的
      • 不仅在堆栈或数据段上。想想一个指向指针数组的指针。在这种情况下,数组中的指针存储在堆上。
      【解决方案8】:
      • 变量/自动变量 ---> 堆栈部分
      • 动态分配的变量 ---> 堆部分
      • 初始化的全局变量 -> 数据部分
      • 未初始化的全局变量 -> 数据部分 (bss)
      • 静态变量 -> 数据部分
      • 字符串常量 -> 文本部分/代码部分
      • 功能 -> 文本部分/代码部分
      • 文本代码 -> 文本部分/代码部分
      • 寄存器 -> CPU 寄存器
      • 命令行输入 -> 环境/命令行部分
      • 环境变量 -> 环境/命令行部分

      【讨论】:

      • 什么是环境/命令行部分?它们存在于 Linux 中吗?
      【解决方案9】:

      带有反汇编分析的 Linux 最小可运行示例

      由于这是标准未指定的实现细节,让我们看看编译器在特定实现上做了什么。

      在这个答案中,我将链接到进行分析的具体答案,或者直接在此处提供分析,并在此处总结所有结果。

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

      函数内的局部变量

      无论是main 还是任何其他功能:

      void f(void) {
          int my_local_var;
      }
      

      如图:What does <value optimized out> mean in gdb?

      • -O0: 栈
      • -O3: 如果不溢出则注册,否则入栈

      有关堆栈存在的动机,请参阅:What is the function of the push / pop instructions used on registers in x86 assembly?

      全局变量和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;
      }
      

      char *char c[]

      如图:Where are static variables stored in C and 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

      我相信这没有什么区别,因为您可以将其类型转换掉。

      相反,如果编译器能够确定某些数据永远不会被写入,那么理论上它可以将其放在 .rodata 中,即使不是 const。

      TODO 分析。

      指针

      它们是变量(包含地址,即数字),因此与其他所有变量一样 :-)

      ma​​lloc

      这个问题对malloc 没有多大意义,因为malloc 是一个函数,并且在:

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

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

      至于 malloc 在内部是如何工作的,当你调用它时,Linux 内核在其内部数据结构上将某些地址标记为可写,当它们最初被程序触及时,就会发生错误,内核启用页表,这让访问在没有 segfaul 的情况下发生:How does x86 paging work?

      但是请注意,这基本上正是 exec 系统调用在您尝试运行可执行文件时所做的事情:它标记要加载到的页面,并将程序写入那里,另请参见:How does kernel get an executable binary file running under linux? 除了exec 对加载位置有一些额外限制(例如代码 is not relocatable)。

      在现代 2020 实现中,用于 malloc 的确切系统调用是 mmap,而在过去使用 brkDoes malloc() use brk() or mmap()?

      动态库

      基本上把mmaped 记入内存: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-storedTODO 为什么不在 .data 中?

      【讨论】:

        猜你喜欢
        • 2010-12-26
        • 1970-01-01
        • 2013-05-10
        • 2013-04-24
        • 2010-09-18
        • 1970-01-01
        • 2010-12-07
        相关资源
        最近更新 更多