内容当然包括代码、数据,还包括链接时所必须的信息:符号表、调试信息、字符串

一般把这些信息按照不同的属性,以(Section)的形式存储,有时候也叫做(Segment)

编译后的代码放在代码段:.code.text

全局变量和局部静态变量数据放在数据段 .data

未初始化的全局变量和局部静态变量放在 .bss (Block Started by Symbol) 因为他们都是 0,所以放在 data 段占内存没必要,做一个预留内存的标记就行了,这样也没占多少空间

为什么要把程序的指令和数据分开放?

当程序被装载的时候,数据和指令分别映射到两个虚拟内存的区域,可以分别设置权限防止程序指令被改写等等提升安全性

对提高 CPU 缓存命中有提升

最重要的原因:当系统运行着多个程序的的副本的时候,他们的指令都是一样的,所以内存中只要保存一份指令就行了,只要数据不同就行,这对内存的节省提升是巨大的

只读数据段 .rodata

还有一些其他常用的段名

gcc 提供了一个拓展机制,可以让程序员制定变量所处的段:

__attribute__((section("FOO"))) int global = 42;
__attribute__((section("BAR"))) void foo(){}

解析一个 elf 可执行文件,通过 elf 文件头可以找到段表的偏移和段表的字符表在段表里的下标,通过段表可以解析出整个 elf 文件段结构

readelf -s main.o

Symbol table '.symtab' contains 14 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
     1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS main.c
     2: 0000000000000000     0 SECTION LOCAL  DEFAULT    1 .text
     3: 0000000000000000     0 SECTION LOCAL  DEFAULT    3 .data
     4: 0000000000000000     0 SECTION LOCAL  DEFAULT    4 .bss
     5: 0000000000000000     0 SECTION LOCAL  DEFAULT    6 .rodata
     6: 0000000000000000     0 SECTION LOCAL  DEFAULT    7 FOO
     7: 0000000000000000     4 OBJECT  LOCAL  DEFAULT    3 static_var.1
     8: 0000000000000004     4 OBJECT  LOCAL  DEFAULT    4 static_var2.0
     9: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    5 global_init_var
    10: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    4 global_uninit_var
    13: 0000000000000000    51 FUNC    GLOBAL DEFAULT    1 main

ndx 是代码所属的段