程序的编译、链接过程详解
gcc -o main main.c
./main
上述过程我们可以分成四个步骤 预编译->编译->汇编->链接
(基准为x86体系下,32位操作系统linux内核)
源代码运行的时候
它的
进程在内存上的布局为
首先我们先写个源程序;
#include<stdio.h>
int a=10; //存放在data
int b=0;//存放在bss
int c;//存放在bss
extern int d;//UND未知
int main()
{
char *p="hello world";//存放在rodata段
int e=a;//代码段
int f=d;//代码段
printf("%d",fun(a,b));//.text
return 0;
}
sum.c
int fun(int a,int b)//.text
{
return a+b;
}
text段存放的是代码段;
rodata段存放在代码段.text,常量不一定就放在rodata里,有的立即数直接编码在指令里;rodata在多个进程间是共享的,可以提高空间利用率;
data段存放的是全局初始化且不为0的常量;
而bss段存放的是全局未初始化或初始化为0的量;
堆区是动态开辟的空间。
而argc和argv是主函数的参数;
第一步主要是下列四个步骤;
预编译:
展开所有的宏定义,处理含有#部分的代码,还有删除所有的注释//和/* */。添加行号和文件名标识。比如#2 “hello .c”还有保留所有的#progma编译器指令。
编译:
进行代码的优化还有符号的汇总(符号表)以及语义分析 语法分析 词义分析及优化后生成相应的汇编代码文件。
汇编:
将指令转成当地操作系统的机器码;
生成可重定向的目标文件:x86体系下的.obj 和unix下的.o文件
问题:为什么.o和.obj文件不能直接执行呢?
因为变量转成符号,符号表的符号地址没有分配出来,所以不能直接执行。
然后进行第二个步骤:
链接过程:
将第一个步骤所生成的.o文件里的段整合在一起,进行【符号解析】,符号解析指的是将每个符号引用刚好和一个符号定义联系起来。对符号表里的未定义的符号找到其定义的地方,代码段中,对所有指令未有其定义的符号都将其变为正确的地址例如extern行的代码以及main函数中的printf。解析正确后,再进行【符号重定位】,对其分配相应的虚拟地址。所有符号都拥有其正确的虚拟地址后,然后生成最终可执行的.exe文件,其运行的时候只需要代码段和数据段。