190

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

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

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

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

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

4

9 回答 9

259

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

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

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

于 2013-01-29T17:39:05.243 回答
161

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

一些提醒:

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

C中的5个内存段:

1.代码段

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

数据段

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

2.未初始化的数据段

  • 该段也称为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* prt = malloc(sizeof(int) * 2)在堆中分配八个字节时,该位置的内存地址将被返回并存储在ptr变量中。根据ptr声明/使用的方式,变量将位于堆栈或数据段上。
  • 进一步阅读
于 2016-01-24T20:46:17.467 回答
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

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

考虑您正在动态分配 n 字节的内存(使用mallocor 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)--------> 堆

于 2013-01-29T17:40:06.547 回答
9

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

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

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

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

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

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

于 2013-01-29T17:42:31.743 回答
7

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

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

于 2013-01-29T17:58:06.807 回答
4

关于存储需要记住的一件事是as-if rule。编译器不需要将变量放在特定的位置——相反,它可以将它放在任何它喜欢的地方,只要编译的程序的行为就像它根据抽象 C 机器的规则在抽象 C 机器中运行一样。这适用于所有存储期限。例如:

  • 一个未被访问的变量可以完全消除——它没有存储......任何地方。示例- 查看生成的汇编代码中如何存在42但没有404.
  • 一个没有被占用地址的具有自动存储持续时间的变量根本不需要存储在内存中。一个例子是循环变量。
  • const一个在内存中或实际上const不需要在内存中的变量。示例- 编译器可以证明这foo是有效const的,并将其使用内联到代码中。bar具有外部链接,编译器无法证明它不会在当前模块之外更改,因此它不是内联的。
  • 分配的对象malloc不需要驻留在从堆分配的内存中!示例- 请注意代码如何没有调用,malloc也没有将值 42 存储在内存中,它保存在寄存器中!
  • 因此,已分配的对象malloc和引用丢失而无需解除分配对象而free 无需泄漏内存...
  • 分配的对象malloc不需要在 Unixen 上的程序 break ( )下面的堆内...sbrk(0)
于 2019-07-06T06:21:41.100 回答
1

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

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

于 2013-01-29T17:35:09.180 回答
1
  • 变量/自动变量--->堆栈部分
  • 动态分配的变量--->堆部分
  • 初始化的全局变量 -> 数据部分
  • 未初始化的全局变量 -> 数据部分 (bss)
  • 静态变量 -> 数据部分
  • 字符串常量 -> 文本部分/代码部分
  • 功能 -> 文本部分/代码部分
  • 文本代码 -> 文本部分/代码部分
  • 寄存器 -> CPU 寄存器
  • 命令行输入 -> 环境/命令行部分
  • 环境变量 -> 环境/命令行部分
于 2014-12-10T07:05:45.610 回答
0

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

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

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

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

函数内部的局部变量

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

void f(void) {
    int my_local_var;
}

如图:gdb中<value优化输出>是什么意思?

  • -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 for X86,它为每个变量指定特定的寄存器或堆栈位置。

然后如What does <value optimize out> mean in gdb 所示?-O0然后将所有内容都放入堆栈中,同时-O3尝试尽可能多地使用寄存器。

但是,如果函数被内联,它们将被视为普通本地人。

const

我相信这没有什么区别,因为您可以将其排版。

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

待办事项分析。

指针

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

malloc

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

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

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

至于 malloc 在内部是如何工作的,当你调用它时,Linux 内核在其内部数据结构上将某些地址标记为可写,并且当它们最初被程序触及时,会发生错误,内核启用页表,从而允许访问没有 segfaul 发生:x86 分页如何工作?

但是请注意,这基本上正是exec系统调用在您尝试运行可执行文件时所做的事情:它标记要加载到的页面,并将程序写入那里,另请参见:内核如何获取运行的可执行二进制文件linux?除了exec对加载到哪里有一些额外的限制(例如,代码是不可重定位的)。

用于现代 2020 实现的确切系统调用mallocmmap过去brk曾使用过:malloc() 使用 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 中?

于 2020-03-18T14:25:52.363 回答