C/C++程序的存储空间布局

有一种是栈,堆,全局区,文字常量区,程序代码区;
还有一种说是栈,堆,未初始化数据段,已初始化数据段,程序代码区。
这两种有什么区别?

阅读 7.6k
6 个回答

全局区包含未初始化数据段,已初始化数据段。

全局区中包含了全局变量和static变量。它们在内存的分布上,已初始化的相邻,未初始化的变量相邻。文字常量区是肯定有的,包含了常量字符串,就是类似 char *s = "Hello, world!"; 这种的。

大体来讲,一个可执行程序包括代码段和数据段。 代码段包含的就是可执行的代码,cpu指令,数据段又可以细分只读已初始数据和读写已初始数据,这2中数据都是包含在可执行文件中的,至于为初始数据,一般可执行文件只记录其大小,然后当程序被载入的时候分配相应的内存,内存应该被清零。具体的内容可以参考ELF文件格式的描述。
栈和堆是程序运行时的概念,表明了程序对内存的两种使用方式。堆指的是c程序在运行时通过malloc方式所获得的内存。栈是函数在运行时用来存储这个函数的临时变量的内存空间。

个人认为从讨论C/C++程序的存储空间布局,本质上还是应该从应用程序进程空间的映射角度去看。
上面所说的各种段和区,本质上都是内存的一部分,只是由于划分的角度不同,所以产生了不同种的说法。

从映射的访问属性来划分

在mmu对进程空间映射时,可以设置虚拟地址对相应物理内存的访问权限,可以设为只读、可读可写。

  • 只读: 正文/指令段、文字常量区
  • 可读可写:剩余的均可读可写,除去在程序运行过程中新建立的只读映射。

从建立映射的时间划分

  • 程序运行过程中建立映射:堆。
  • 程序运行前建立映射:除堆外的其它区域。

从控制权来划分

  • 系统:映射为只读的区域(正文/指令段、文字常量区)。
  • 编译器控制:堆栈(编译器负责压栈与出栈的操作)、全局变量。
  • 用户控制:堆。(用户拥有对堆分配与回收的权利与责任)

程序中隐藏的bug往往是出在堆与指针上面,因为很多用户没有正确地去分配、使用与回收。在C/C++之后的高级语言中大多数都吧堆的控制权转移到编译器上。由于用户失去了对所有内存的控制权,指针也就没有存在的必要了,所以大部分高级语言同时不再提供指针了。这就大大减少了程序员的犯错率。

从生命周期划分

这取决于上面的控制权问题,由谁控制就取决于谁,

  • 由用户控制的区域声明周期由用户决定(给了用户自由,也给了用户更多犯错的机会)。
  • 由编译器控制的堆栈,其生命周期就是其翻译到汇编之后push与pop之间的区域;全局变量,其生命周期就是编译连接之后的整个程序内部。
  • 由系统控制的正文段、常量。其生命周期就是当前进程。

回答问题与总结

上面所说的几种划分方法、相互交叉。程序中的每个变量,都具有上面所说的几种属性。我们的目的是正确的使用变量,通过上面所说的属性我们可以知道每个变量的的适用范围从而正确的使用变量,无需刻意地为变量划分各种类别。

心得

从上面的进程内存布局可以看出,C/C++给了程序员太多的自由去操作内存,以达到让程序员去编写更高效的程序。但是这也往往会让经验不够丰富的程序员在编写程序时埋下很多bug,这些bug是随机在运行时产生的,很难发现。
现在cpu的速度越来越快,程序的效率显得不再那么至关重要了。大部分高级语言都已经将内存封装起来,由解释器或编译器去控制,将写程序的难度很大一部分转移到了语言创作者的身上,让程序员去任意地使用变量,而不用过多地去考虑内存的问题。但是理解内存分部仍然是很有必要的。

两种说法没区别, 第二种更详细:
栈(stack),堆(heap),未初始化数据段(.bss),已初始化数据段(.data),程序代码区(.text)。
.bss也叫" Block Started by Symbol" 链接
你可以用readelf查看"可执行程序"的结构:
比如一个简单的c代码, 编译:gcc test.c -o test, 编译出可执行文件test
然后readelf -ahW test来查看:

# readelf -ahW test
Section Headers:
[Nr] Name Type Address Off Size ES Flg Lk Inf Al
...
[13] .text PROGBITS 00000000004003d0 0003d0 0001d4 00 AX 0 0 16
...
[15] .rodata PROGBITS 00000000004005b0 0005b0 000024 00 A 0 0 8
...
[24] .data PROGBITS 00000000006008c0 0008c0 000008 00 WA 0 0 4
[25] .bss NOBITS 00000000006008c8 0008c8 000008 00 WA 0 0 4
...
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), l (large)
I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)

第2列就是上面提到的每个"区域", 第三列十六进制数字是偏移地址.
就酱

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题