在Linux下编译链接程序时,如果不用‘-o’选项来指定输出文件名称,默认情况下就会输出名为‘a.out’的文件。为什么默认是‘a.out’而不是别的名称呢?这是一个历史遗留问题。
在早期的BSD文档里有下面的提示:
a.out —— 汇编程序和链接编辑输出格式。
可以看出它是一种输出格式,与EFL格式、二进制格式等是并列的。a.out是‘assembler output(汇编程序输出)’的缩写形式。以前并不存在连接器,程序一般是这样创建的:先把所有源文件连接在一起,然后进行汇编,汇编产生的汇编程序输出保存在a.out中。等有了连接器后,仍然保留了“输出文件默认为a.out”的命名习惯。
现在,a.out被普遍使用的ELF格式所替代,但输出文件名仍旧是a.out。我们看到的a.out只是一个可执行文件,而不再是文件格式。ELF可执行文件的第一个字节是八进制177,紧跟其后的2、3、4字节是ELF三个字母。可输入od -c a.out | head 查看。
既然是一种输出格式,那么它都是由哪几部分组成的呢?它一般由下面几部分组成:
执行头部:包含内核将二进制文件加载内存并执行所需的参数,也包含对链接编辑器ld的指引。
文本段:包含运行时被载入内存的机器码的相关数据,可能是只读的。
数据段:包含已初始化的数据,总是可写的。
文本重定位:包含链接编辑器在合并二进制文件时修改文件段指针的记录。
数据重定位:与文本重定位类似,但是给数据段指针用。
符号表:包含链接编辑器用于交叉引用不同二进制文件中变量和函数的记录。
字符串表:包含对应于符号表的字符串。
以一个程序out.c为例分析各部分会出现在哪些段中:
#include <malloc.h> char pear[40]; static double peach; int mango = 13; static long melon = 2001; void main() { int i = 3, j, *ip; ip = malloc(sizeof(i)); pear[5] = i; peach = 2.0 * mango; }
图 out.c程序语句的各部分会出现在哪些段中
为什么a.out要以段的形式组织。段可以方便地映射到链接器在运行时可以直接载入的对象中。载入器只是取文件中每个段的映像,并直接将它们放入内存中,从本质上说,段在正在执行的程序中是一块内存区域,每个区域都有特定的目的。
文本段包含程序的指令。连接器把指令直接从文件拷贝到内存中,以后便再也不用管它。数据段包含经过初始化的全局和静态变量以及它们的值。BSS段的大小从可执行文件中得到,然后链接器得到这个大小的内存块,紧跟在数据段之后。