摘要:总结了编译器编译的整个过程以及链接器的职责,分别介绍了每一步具体做了哪些事,最后通过一个实例演示了这个过程。
一、整个编译过程描述
这里有一幅图,可以描述整个编译过程:
预处理:gcc –E file.c –o hello.i
1.处理所有的注释,以空格代替。
2.将所有的#define删除,并且展开所有的宏定义。
3.处理条件编译指令#if, #ifdef, #elif, #else, #endif。
4.处理#include,展开被包含的文件。
5.保留编译器需要使用的#pragma指令。
编译:gcc –S file.c –o hello.s
1.对预处理进行词法分析,语法分析和语义分析。
词法分析:分析关键字,标示符,立即数等是否合法。
语法分析:分析表达式是否遵循语法规则。
语义分析:在语法分析的基础上进一步分析表达式是否合法。
分析结束后进行代码优化生成相应的汇编代码文件。
汇编:gcc –c file.c –o hello.o
1.汇编器将汇编代码转变为机器可以执行的指令。
2.每个汇编语句几乎都对应一条机器指令。
链接器
连接器的主要作用是把各个模块之间相互引用的部分处理好,使得各个模块之间能够正确的衔接。
二、实例分析编译过程
例程file.c如下:
<span style="font-size:18px;">//#include <stdio.h>
#include "file.h"
#define LOW 0
#define HIGH 255
int max(int a, int b)
{
return MAX(a,b);
}
int main()
{
int c = max(LOW, HIGH); // Call max to get the larger number
return 0;
}</span>
file.h如下:
<span style="font-size:18px;">/*
This is the header file for test.c
*/
#define MAX(a,b) (((a)>(b)) ? (a) : (b))
int g_global = 10;</span>
第一步,预处理,预处理做了什么呢?我们用预处理指令操作,看得到了什么。
#gcc–E file.c –o file.i
生成的file.i文件如下:
<span style="font-size:18px;"># 1 "file.c"
# 1 "<built-in>"
# 1 "<命令行>"
# 1 "file.c"
# 1 "file.h" 1
/*所有注释被以空格代替,这里第一行的#include <stdio.h>没有被展开是因为注释会被空格代替*/
int g_global = 10;//include中的内容被展开
# 3 "file.c" 2
int max(int a, int b)
{
return (((a)>(b)) ? (a) : (b));//#define宏被删除并展开
}
int main()
{
int c = max(0, 255);//#define的值这里直接替换到程序里,本身被删除
return 0;
}</span>
下一步编译,使用命令:
#gcc –S file.c –o file.s
得到的内容如下:
<span style="font-size:18px;"> .file "file.c"
.globl g_global
.data
.align4
.type g_global, @object
.size g_global, 4
g_global:
.long 10
.text
.globl max
.type max, @function
max:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %eax
cmpl %eax, 12(%ebp)
cmovge 12(%ebp), %eax
popl %ebp
ret
.size max, .-max
.globl main
.type main, @function
main:
pushl %ebp
movl %esp, %ebp
subl $24, %esp
movl $255, 4(%esp)
movl $0, (%esp)
call max
movl %eax, -4(%ebp)
movl $0, %eax
leave
ret
.size main, .-main
.ident "GCC: (GNU) 4.5.1 20100924 (Red Hat4.5.1-4)"
.section .note.GNU-stack,"",@progbits</span>
编译的步骤其实是进行一系列词法语法语义分析之后,将c语言翻译到汇编语言。
汇编步骤,使用下面的指令:
#gcc file.c –o file.o
这时候生成了一个file.o文件如下:
这里指的注意的是,为什么file.o的颜色和其他之前两个生成的不一样,因为它具有可执行的权限,到了这一步,file.o里面就是机器可以执行的指令了,这时候用ue打开是这样的:
链接器的行为,因为一个代码不可能一个文件写完,我们会按照功能分开写,这时候链接器就有组织有规律的把这些模块组装起来,这里比如函数调用了libc库,链接器可以用动态方式和静态方式进行链接,关于什么是动态链接什么是静态链接,我的别的帖子详细介绍了,这里不多做赘述。无论是静态还是动态,链接器连接起来之后,我们就得到了最终的可执行的文件,file,或者默认的a.out。
最后总结一下:
编译器主要工作分为三步:第一,预处理;第二,编译;第三,汇编。
链接器把各个独立的模块链接成可执行文件。
这篇帖子就总结到这里吧,如有不正确的地方,还请指出,大家共同进步!