【翻译】 Memory Layout of C Programs

Memory Layout of C Programs
原文链接:http://www.geeksforgeeks.org/memory-layout-of-c-program/

《c程序内存布局》
一个C程序的典型内存表示由以下部分组成。

  1. 文本段(Text Segment)
  2. 初始化数据段(Initialized data segment)
  3. 未初始化数据段(Uninitialized data segment)
  4. 栈(Stack)
  5. 堆(Heap)

Memory-Layout
一个运行时进程的典型内存布局

  1. 文本段(Text Segment): 文本段,也被称为代码段或简称文本,是对象文件或内存中程序的一个组成部分,主要包含可执行的指令。

作为一个内存区,一个文本段可以放在堆或栈的下面以阻止堆或栈的溢出。

经常,文本段是共享的,只有一个副本会被放在内存里供高频执行的程序,比如文本编辑器,C编译器,Shell,等等来使用。而且,文本段常常是只读的,用以阻止一个程序意外修改它的指令。

  1. 初始化数据段(Initialized Data Segment): 初始化数据段,常常被简称为数据段。一个数据段就是一个程序所占的一份虚拟地址空间,包含了那些被程序员所初始化的全局变量与静态变量。

记住,数据段并不意味着它是只读的,那些变量的值会在运行时刻被改变。

这个段可以细分为“初始化只读区”与“初始化读写区”两部分。

举个例子,那些在全局定义的字符串如char s[] = “hello world”以及main函数外的C赋值语句int debug=1,会被存储在初始化读写区。而一个全局的C语句如const char* string = “hello world”则会使常量字符串”hello world”被存在初始化只读区,而char指针变量会被存在初始化读写区。

例: static int i = 10 会被存储在数据段,全局的int i = 10也会被存储在数据段。

  1. 未初始化数据段(Uninitialized Data Segment): 未初始化数据段,经常被称作"bss"段,命名自一个古老的汇编伪指令bss,愿意是“block started by symbol.”这个段里的数据会在程序运行前由内核来初始化为0。

未初始化的数据从数据段的末尾开始,包括那些在源码中被初始化为0或没有被初始化的全局变量与静态变量。

举例而言,一个变量声明如static int i;会被包含在BSS段。
又例,一个全局变量声明如int j;也会包含在BSS段。

  1. 栈(Stack): 传统上一个栈区与堆区相邻,而且相互对向增长;当一个栈指针与堆指针相遇时,空闲的内存便被耗尽了。(在现代大地址空间与虚拟内存技术下,它们可能会被放在任何可能位置,但依然会相互对向增长。)

栈区域包含了程序栈,一个后进先出(LIFO)的结构。它常常被放在内存的高地址部分。在标准的x86计算机结构里,它会向0地址方向增长;而在一些其它的结构里,它可能是反的。会有一个“栈指针(stack pointer)”寄存器用来跟踪栈顶;它会在每次一个值被”push”入栈后被调节。每一次函数调用导致入栈的一组值,在术语上被称作“栈帧”;一个栈帧至少要包含该函数的返回地址。

栈,是存储自动变量(译者注:方法局部变量)的,每次函数被调用这些变量都会保存。每一次函数调用,返回地址与调用者的明确环境信息,比如一些机器寄存器,会被存在栈里。新调用的函数然后会分配一些栈空间给它的自动与临时变量。这正是C语言里递归函数所用的。每次一个递归函数调用自己时,一个新的栈帧会被创建,所以它的一组变量不会被另一个函数实例中的变量所影响。

  1. 堆(Heap): 堆与一个由动态内存分配所占用的段。

堆区从BSS端的末尾开始,逐渐向大地址方向增长。堆区由malloc,realoc以及free来管理,这些函数底层会调用brk、sbrk这种系统调用(system call)来调节大小(注意使用brk/sbrk来实现的一个单独的”堆区”并不一定满足malloc/realloc/free的条约;它们也可以用mmap()把一块虚拟内存上的可能逆向的非连续区域映射到虚拟地址空间上来。)堆区由一个进程中所有共享库与动态加载的模块共享。

例子

size命令可以报告一个文本,数据,或BSS段的大小(用字节表示)(详细信息请参考size命令的man手册)

  1. 检查一下这个简单的C程序。
#include <stdio.h>

int main(void)
{
    return 0;
}
[narendra@CentOS]$ gcc memory-layout.c -o memory-layout
[narendra@CentOS]$ size memory-layout
text       data        bss        dec        hex    filename
960        248          8       1216        4c0    memory-layout
  1. 添加一个全局变量,然后再检查一下bss段的大小。
#include <stdio.h>

int global; /* 存在BSS段的未初始化全局变量 */

int main(void)
{
    return 0;
}
[narendra@CentOS]$ gcc memory-layout.c -o memory-layout
[narendra@CentOS]$ size memory-layout
text       data        bss        dec        hex    filename
 960        248         12       1220        4c4    memory-layout
  1. 添加一个静态变量,也会存在bss段。
#include <stdio.h>

int global; /* 存在BSS段的未初始化全局变量 */

int main(void)
{
    static int i; /* 存在BSS段的未初始化静态变量 */
    return 0;
}
[narendra@CentOS]$ gcc memory-layout.c -o memory-layout
[narendra@CentOS]$ size memory-layout
text       data        bss        dec        hex    filename
 960        248         16       1224        4c8    memory-layout
  1. 把那个静态变量初始化一下,它就会被存储在数据段(DS)。
#include <stdio.h>

int global; /* 存在BSS段的未初始化全局变量 */

int main(void)
{
    static int i = 100; /* 存在DS段的已初始化静态变量 */
    return 0;
}
[narendra@CentOS]$ gcc memory-layout.c -o memory-layout
[narendra@CentOS]$ size memory-layout
text       data        bss        dec        hex    filename
960         252         12       1224        4c8    memory-layout
  1. 把那个全局变量初始化一下,它也会被存储在数据段(DS)。
#include <stdio.h>

int global = 10; /* 存在DS段的已初始化全局变量 */

int main(void)
{
    static int i = 100; /* 存在DS段的已初始化静态变量 */
    return 0;
}
[narendra@CentOS]$ gcc memory-layout.c -o memory-layout
[narendra@CentOS]$ size memory-layout
text       data        bss        dec        hex    filename
960         256          8       1224        4c8    memory-layout

这篇文章由Narendra Kangralkar所撰写。如果你发现什么错误之处,请去原文链接下发表评论,或者针对这个话题有所高见,也可去该处讨论。

参考:
http://en.wikipedia.org/wiki/Data_segment
http://en.wikipedia.org/wiki/Code_segment
http://en.wikipedia.org/wiki/.bss
http://www.amazon.com/Advanced-Programming-UNIX-Environment-2nd/dp/0201433079