1.概述
一个C语言程序从源代码文件变成最后的可执行程序文件,需要经历预处理、编译、汇编、链接四个过程。
- 预处理:条件编译、头文件包含、宏替换,生成.i文件
- 编译:将预处理后的文件转换成汇编语言,生成.s文件
- 汇编:汇编变为目标代码(机器代码)生成.o文件
- 链接:链接目标代码,生成可执行程序。
日常使用GCC编译器完成上述过程,需要以下选项:
-E Preprocess only; do not compile, assemble or link
-S Compile only; do not assemble or link
-c Compile and assemble, but do not link
-o <file> Place the output into <file>
2.编写C语言测试程序
<hello.c>
#include<stdio.h>
#define FILE_NAME "hello.c"
int main()
{
int condition = 1;
if(condition) {
printf("%s,case 1 hello world\n", FILE_NAME);// hello.c,case 1 hello world
} else {
printf("%s,case other hello world\n", FILE_NAME);//hello.c,case other hello world
}
return 0;
}
3.使用GCC进行编译
3.1 预处理
gcc -E hello.c -o hello.i
GCC参数-E表示只进行预处理
cat hello.i
我们可以看到预处理后的代码
<hello.i>
......
extern FILE *popen (const char *__command, const char *__modes) ;
extern int pclose (FILE *__stream);
extern char *ctermid (char *__s) __attribute__ ((__nothrow__ , __leaf__));
# 912 "/usr/include/stdio.h" 3 4
extern void flockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));
extern int ftrylockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ;
extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));
# 942 "/usr/include/stdio.h" 3 4
# 2 "hello.c" 2
# 5 "hello.c"
int main()
{
int condition = 1;
if(condition) {
printf("%s,case 1 hello world\n", "hello.c");
} else {
printf("%s,case other hello world\n", "hello.c");
}
return 0;
}
通过对hello.i文件查看,原本几行的代码变成了几百行,而且include关键字也不见了,取而代之的是stdio.h的路径和内容,可以发现预处理过程完成宏的替换、注释的消除、头文件的展开等。
预处理完成的工作
- 将所有的#define删除,并且展开所有的宏定义
- 处理所有的条件预编译指令,比如#if #ifdef #elif #else #endif等
- 处理#include 预编译指令,将被包含的文件插入到该预编译指令的位置。
- 删除所有注释 “//”和”/* */”.
- 添加行号和文件标识,以便编译时产生调试用的行号及编译错误警告行号。
- 保留所有的#pragma编译器指令,因为编译器需要使用它们
3.2 编译
gcc -S hello.i -o hello.s
GCC参数 -S表示只进行编译过程,不进行汇编和链接
cat hello.s
.file "hello.c"
.section .rodata
.LC0:
.string "hello.c"
.LC1:
.string "%s,case 1 hello world\n"
.LC2:
.string "%s,case other hello world\n"
.text
.globl main
.type main, @function
.file "hello.c"
.section .rodata
.LC0:
.string "hello.c"
.LC1:
.string "%s,case 1 hello world\n"
.LC2:
.string "%s,case other hello world\n"
.text
.globl main
.type main, @function
main:
......
我们可以看到hello.s的文件内容是优化后的汇编代码。
编译过程就是把预处理完的文件进行一系列的词法分析,语法分析,语义分析及优化后生成相应的汇编代码。
汇编语言是汇编指令集、伪指令集和使用它们规则的统称,使用具有一定含义的符号为助忆符,用指令助忆符、符号地址等组成的符号指令称为汇编格式指令。
简单的可以理解为汇编语言是一本词典,01100101011010这样的二进制字符串是单词,汇编指令是单词的含义。计算机能读懂二进制字符串,而人能读懂的是翻译过来的汇编指令
3.3汇编
gcc -c hello.s -o hello.o
GCC参数-c表示汇编,将汇编文件编译成目标代码(机器代码,二进制101010110)
cat hello.o
^?ELF^B^A^A^@^@^@^@^@^@^@^@^@^A^@>^@^A^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@`^C^@^@^@^@^@^@^@^@^@^@@^@^@^@^@^@@^@^M^@
^@UH<89>åH<83>ì^PÇEü^A^@^@^@<83>}ü^@t^V¾^@^@^@^@¿^@^@^@^@¸^@^@^@^@è^@^@^@^@ë^T¾^@^@^@^@¿^@^@^@^@¸^@^@^@^@è^@^@^@^@¸^@^@^@^@ÉÃhello.c^@%s,case 1 hello world
^@%s,case other hello world
^@^@GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.10) 5.4.0
可以看到打印许多不可见的字符,原因是目标已经是二进制格式的,不同于源代码(文本格式)
汇编器是将汇编代码转变成机器可以执行的命令,每一个汇编语句几乎都对应一条机器指令。汇编相对于编译过程比较简单,根据汇编指令和机器指令的对照表一一翻译即可。
。
对于被翻译系统处理的每一个C语言源程序,都将最终经过这一处理而得到相应的目标文件。
目标文件中所存放的也就是与源程序等效的目标的机器语言代码。
汇编程序生成的目标文件实际上是可重定位文件,它其中包含有适合于其它目标文件链接来创建一个可执行的或者共享的目标文件的代码和数据。
也就是说,由汇编程序生成的目标文件并不能立即就被执行,它并非最终可执行的二进制序列,因为其中可能还有许多没有解决的问题。那么这个就是链接程序的工作了。
3.4链接
gcc hello.s -o hello
通过调用链接器ld来链接程序运行需要的一大堆目标文件,以及所依赖的其它库文件,最后生成可执行文件。
例如hello.c程序中使用c标准库中的printf,在链接阶段就需要将汇编生成的目标机器代码与对应的库绑定在一起。使用ldd命令即可以查看可执行程序链接的库。
ldd hello
cmh@ubuntu:~/project/gdb$ ldd hello
linux-vdso.so.1 => (0x00007fff6ab28000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f1ed58d4000)
/lib64/ld-linux-x86-64.so.2 (0x00007f1ed5c9e000)
链接的主要过程包括:地址和空间分配(Address and Storage Allocation),符号决议(Symbol Resolution),重定位(Relocation)等。
链接器负责将程序的目标文件与所需的所有附加的目标文件连接起来,最终生成可执行文件。附加的目标文件包括静态连接库和动态连接库。
链接分为静态链接和动态链接。
静态链接是指在编译阶段直接把静态库加入到可执行文件中去,这样可执行文件会比较大。
而动态链接则是指链接阶段仅仅只加入一些描述信息,而程序执行时再从系统中把相应动态库加载到内存中去。
3.5总结
-rwxrwxr-x 1 cmh cmh 8608 Oct 13 20:49 hello*
-rw-rw-r-- 1 cmh cmh 285 Oct 13 19:49 hello.c
-rw-rw-r-- 1 cmh cmh 17228 Oct 13 19:50 hello.i
-rw-rw-r-- 1 cmh cmh 1696 Oct 13 20:19 hello.o
-rw-rw-r-- 1 cmh cmh 714 Oct 13 20:03 hello.s
可以发现hello.o二进制目标文件只有1.6k,hello可执行文件大小为8k多,这就是链接过程链接的库文件。