in Linux

[译] Memory Layout of C Programs

本文译自 Memory Layout of C Programs

一个典型的 C 程序内存特征将会有如下几个区域:

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

一个典型的运行进程的内存布局:

1. 文本段(Text Segement)

文本段也被成为代码段(code segment)或者简称为文本(text),它是一个程序在一个对象文件(object file)或在内存中的一部分。

作为一块内存区域,为了防止堆或栈溢出时重写文本段,它可能会被放置在堆或栈的下面。

通常情况下,文本段是可共享的,因此它只能有一个副本需要成为在内存中用于频繁的执行程序(the text segment is sharable so that only a single copy needs to be in memory for frequently executed programs),例如文本编辑器,C 编译器,Shell等等。同样的,文本段通常是只读的,用于预防程序在意外情况下修改它的指令。

2. 已初始化数据段(Initialized Data Segment)

已初始化数据段,通常被简单称为数据段(Data Segment)。数据段是程序虚地址空间的一部分,它包含了被程序员初始化过的全局变量和静态变量(static variables)。

记住,数据段不是只读的,因为(Since)变量的值能够在运行时被改变。

这个段(segment)可进一步分为已初始化只读区已初始化读写区

例如全局字符串定义char s[] = "hello world"、以及在main外部(例如全局)声明int debug=1将被存储在已初始化读写区。而一个全局 C 声明像是const char* string = "hello world",字符串"hello world"将存储在已初始化只读区,而字符串指针变量(character pointer variable string)位于已初始化读写区

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

3. 未初始化数据段(Uninitialized Data Segment)

未初始化数据段,通常被称为”bss”段,以 一个古老的汇编操作符 代表 以符号为开端的区块(短语 Block Started by Symbol) 命名(译者注:这就是 bss 的缩写啦)。这个段的数据在程序开始执行之前被内核初始化为0。

未初始化数据起始于数据段的结尾,它包括所有那些 被初始化为0或未被明确在代码中初始化的 全局变量和静态变量。(uninitialized data starts at the end of the data segment and contains all global variables and static variables that are initialized to zero or do not have explicit initialization in source code.)

例如一个变量声明static int i;将被包含在 BSS 段。

例如一个全局变量声明int j;将被包含在 BSS 段。

4. 栈(Stack)

栈区通常紧挨着堆区并在相反方向增长;当栈指针遇到堆指针,可用内存就耗尽了。(随着现代大地址空间和虚拟内存技术,它们可能被放置在几乎任何地方,但是它们通常仍然会在相反方向增长。)

栈区包含了程序栈,一个 LIFO 结构,通常位于内存的最顶部。在标准PC x86计算机结构中它向地址0的方向增长;在其他结构中它向相反方向增长。“栈指针”用于追踪记录栈顶;它在每次value入栈(pushed into)时被改变。这整套用于一个函数pushed的值被称为栈帧(stack frame);一个栈帧至少包含一个返回地址(return address)。

栈,它是自动变量的存储位置,以及被每次函数被调用时保存的信息。每次函数被调用时的返回地址,以及关于调用者的环境信息,例如一些机器寄存器,都将被储存在栈中。新的被调用函数随后分配空间在栈用于保存它的自动和临时变量。这就是 C 语言的递归函数的工作原理。每次递归函数调用自身,一个新的栈帧就被使用,因此整组变量不能干涉另一个函数实体的变量。

5. 堆(Heap)

堆通常用于动态内存分配。

堆区开始于 BSS 段结尾并且向大地址方向增长。堆区使用mallocreallocfree进行管理,它们可能使用brksbrk系统调用来调整它们的大小(注意,使用 brk/sbrk 和 一个单个的堆区 对于实现 malloc/realloc/free 来说不是必须的;它们也可能使用 mmap 去预留潜在的非连续的虚拟内存区域到进程(process)的虚拟地址空间 [这这……我真的翻译不能了……求指教……])。堆区由进程中的所有共享库和动态加载模块共享。(The Heap area is shared by all shared libraries and dynamically loaded modules in a process.)

译者注:堆区的调整方式有两种:brk/sbrkmmap,它们都是系统调用。对于 256字节以上但低于 mmap 门槛(通常是256KB)的要求,使用in-place bitwise trie algorithm算法,如果剩余空间不能满足要求,dlmalloc 将试图增加堆的大小,通常通过 brk 系统调用。对于超过 mmap 门槛的内存请求,总是使用 mmap 系统调用。(引用并翻译自 Wikipedia

下面是一些例子。

size(1) 命令报告了text,data 以及 bss 段的(字节)大小(更多细节请参考 man 手册)。

_1. 检查以下简单的 C 程序

_2. 让我们在程序中添加一个全局变量,现在检查 bss 段的大小(请注意观察高亮行的 bss 列值)

_3. 让我们添加一个静态变量,它也保存在 bss 段。

_4. 让我们初始化这个静态变量,它将被存储在数据段(DS)

_5. 让我们初始化全局变量,它将被存储在数据段(DS)

这篇文章编撰于Narendra Kangralkar。如果你发现任何错误,或者你想要分享更多关于上方主题讨论的信息,请留下评论。

参考资料

原创文章,采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。
转载请注明:转载自 Tony's blog,原文网址:https://itony.me/849.html