以一个简单的helloworld.c为例:
#include <stdio.h>
int main(void)
{
print("hello world\n");
}
要将上诉程序编译位一个可执行的elf文件,需要经历如下步骤预处理(Prepressing),编译(Compilation),汇编((Assembly),链接((Linking)。
1 . 预处理(Prepressing) 过程处理文件中的预处理命令,通常以#号开头(注意#pragma位编译器指令 ,编译会使用到),生成 .i 文件。一般步骤如下:
1.1 展开宏定义并伤处 #define 语句
1.2 处理条件预编译指令如 #if
1.3 处理#include 指令将需要包含的文件递归的包含进来
1.4 删除注释语句
1.5 添加行号和文件名称标识,以便编译器使用如遇到编译错误时输出文件信息
使用-E 命令作预处理操作:
gcc -E -o helloworld.i helloworld.c
2. 编译阶段,编译阶段时整个过程中比较复杂的部分,编译器会将预处理之后的文件的内容,经过词法分析,得到所需要的Tokens,然后作对于的语法解析,语义解析,最后产生 .s结尾的汇编文件。
使用-S命令作编译处理:
gcc -S -o helloworld.s helloworld.c
3. 汇编阶段,经过编译阶段得到的.s文件,里面是一行行的汇编代码,基本条汇编代码会对应一条机器指令,所以汇编过程其实就是简单将每条汇编代码翻译成对应的机器指令就行,类似于一个查表过程。当然汇编代码中有许多符号链接,这些符号链接代表了一个个的地址,汇编器需要负责计算出这些符号所代表的地址。
使用-c 命令作汇编操作
gcc -c helloworld.c -o helloworld.o
4. 链接阶段 , 汇编阶段产生的.o文件是一个可重定位的文件,这些文件中的符号地址是没有被确定下来的,如果是文件内定义的符号,因为在文件内偏移是固定的,所以可以通过短跳转指令,找到这些符号。当是如果引用的是其他文件中的符号,则需要在链接的时候才能确定符号的具体地址,甚至如果引用的是动态库中的符号,则只能在程序运行时才能确定符号地址。
单独的链接指令一般很少使用,因为需要链接很多其他的我们不怎么关心的依赖库,如果想要链接.o 文件可以直接使用gcc命令如:
gcc helloworld.o -o helloworld