C-从源文件到可执行文件的详细编译链接过程

一直用windows一键搞定, 没有去了解详细的编译链接过程, 今天看了一篇文章, 顺便实验和记录在Linux下逐步生成的步骤.

预处理: 执行#include, #define, #if, #ifdef等预处理指令 把宏展开

编译: 把源文件编译为汇编语言文件 对所有常量表达式(只包含常量的表达式)求值发生在此阶段(不是预处理阶段)

汇编: 把汇编语言文件翻译称为机器语言指令

链接: 连接器就负责处理合并各种用到的*.o, 比如用到的printf函数就会连接printf.o, 结果就得到一个可执行文件, 可以被加载到内存中由系统执行

先说下gcc常用选项

  --version: 查看gcc版本号及版权信息

  -x language: 指明使用的编程语言, 允许的语言包括c、c++、assembler none,  ‘none’意味着恢复默认行为, 即根据文件的扩展名猜测源文件的语言

  -o: *输出到指定文件 (与其他选项配合生成指定步骤下的文件)

  -E: *仅做预处理, 不进行编译、汇编和链接, 即执行#include, #define, #if, #ifdef等预处理指令

  -S: *仅编译到汇编语言, 不进行汇编和链接, 即把源文件翻译为汇编语言

  -c: *编译、汇编到目标代码(目标代码可不是执行文件), 不进行链接, 从-E、-S到-c命令执行的步骤范围从小到大

  -pipe: 使用管道代替临时文件

  -combine: 将多个源文件一次性传递给汇编器

  **如果不指定参数则自动执行预处理、编译到汇编语言、汇编到目标代码、链接生成可执行文件**

  

  -l library或者-llibrary: 进行链接时搜索名为library的库, gcc hello.c -lm -o hello

  -Idir: 把dir加入到搜索头文件的路径列表中, gcc hello.c -I../inc -o hello

  -Ldir: 把dir加入到搜索库文件的路径列表中, gcc -I/home/foo -L/home/foo -ltest test.c -o test

  -g: 表示在生成的目标文件中带调试信息, 调试信息可以在程序异常中止产生core后, 帮助分析错误产生的源头, 包括产生错误的文件名和行号等非常多有用的信息

  -Wall: 会打开一些很有用的警告选项, 建议编译时加此选项

  -w: 禁止显示所有警告信息

先来个hello world

1 #include <stdio.h>
2 main() {
3     printf("Hello World!
");
4 }

将上面内容保存为hello.c, 并通过gcc去编译它

  gcc -g -Wall hello.c -o hello

执行完上述语句后程序会报错

  

  hello.c:3: warning: return type defaults to ‘int’ /*当函数没有设置返回值类型的时候, C语言默认程序返回的是int类型*/

  hello.c:5: warning: control reaches end of non-void function /*警告: 在有返回值的函数中, 控制流程到达函数尾 [-Wreturn-type]*/

解决上述问题的方法很简单, 代码如下

1 #include <stdio.h>
2 int main() {
3     printf("Hello World!
");
4     return 0;
5 }

再次执行gcc -g -Wall hello.c -o hello

  

下面就来详细讲解整个编译过程

  

上图是一个hello的c程序由gcc编译器从源码文件hello.c中读取内容并将其翻译成为一个可执行的对象文件hello的过程, 这个过程包含了几个阶段:

  1.预处理过程

    预处理根据以字符#开头的命令修改原始的C程序, 比如hello.c中的第一行的#include <stdio.h> 命令告诉预处理器读取系统头文件stdio.h的内容, 插入到程序文本中, 结果就得到里另一个C程序, 通常是以*.i作为文件扩展名

    可通过如下指令获得gcc -E hello.c -o hello.i

    

    用记事本打开hello.i可以看到如下代码(注意行数)

    

    

    可以看到除了#include <stdio>指令之外, 其他指令均未被改变

  2.编译阶段(即把源文件转为汇编语言): 编译器将文本文件hello.i翻译成文本文件hello.s, 它包含一个汇编语言程序, 汇编语言程序中的每条语句都以一种标准的文件格式确切地描述了一条低级机器语言指令

    可通过如下指令获得: gcc -S hello.c -o hello.s

    打开hello.s可以看到

    

  3.汇编阶段(把汇编代码翻译称为机器语言指令): 汇编器将hello.s翻译成机器语言指令, 把这些指令打包成一种可重定位目标程序的格式, 并把结果保存在hello.o中, hello.o是一个二进制文件, 它的字节编码是机器语言指令, 而不是字符

    可通过如下指令获得: gcc -c hello.c, 最终生成的hello.o文件需要使用objdump打开, 具体指令为: objdump -d hello.o, 所查看到的内容为

    

  4.链接阶段: hello.c程序中调用了printf函数, 而printf函数存在于一个名为printf.o的单独的预编译好的目标文件中, 而这个文件必须以某种方式合并到我们的hello.o程序中, 连接器就负责处理这种合并, 结果就得到hello文件, 它是一个可执行文件, 可以被加载到内存中由系统执行

    使用的指令为: gcc hello.o -o hello, 最终生成了一个hello文件, 同样此hello文件可用通过objdump打开: objdump -d hello

经过上面四个过程, 我们就可以把一个源代码文件编译成机器能运行的可执行文件, 这个可执行文件刚开始是保存在磁盘上, 当计算机要运行这个程序的时候, hello就被加载到内存中, 接着程序指令被不断复制到寄存器中由CPU来执行, 最后把”hello world”从寄存器中打到显示设备上, 这就是hello程序整个执行过程

参考: http://blogread.cn/it/article/6492?f=wb1

原文地址:https://www.cnblogs.com/JohnABC/p/3415598.html