当我们写完代码编译的时候,计算机都进行了哪些步骤呢?这些步骤又都有些什么作用呢?
一、执行一个程序的过程
当我们编写一个程序并编译执行,如下(hello.c)
#include <stdio.h> int main() { printf("Hello World "); return 0; }
/*编译执行*/ $gcc hello.c -o hello.out $./hello.out Hello World
那么在其中执行了如图的过程:
其中的主要过程包括:
预处理(Propressing):处理"#define、#include、#if……"等,删除注释,添加行号,但保留所有#pragma编译器指令;
编译(Compilation):词法分析、语法分析、语义分析及优化后产生汇编代码文件;
汇编(Assembly):将汇编代码转换成机器可以执行的指令;
链接(Linking):将所有需要的静态、动态库、文件等链接成为可执行程序。
二、编译过程
也就是上图中的编译部分,主要过程共分为6步,如下图:
扫描(Scanner):用类似于有限状态机,把代码分割成一系列记号(Token);
语法分析(Parser):通过对记号进行分析,生成语法树(Syntax Tree);
语义分析(Semantic Analyzer):完成表达式语法层面的分析,并不关心意义;如不会处理两个指针相乘;
源代码优化(Source Code Optimizer):将语法树转换成中间代码,如三地址码 x = y op z;
代码生成(Code generator): 初步生成目标代码;
目标代码优化(Code Optimizer):将目标代码优化为最终的代码。比如选择合适的寻址方式、使用位移来代替乘法运算、删除多余指令等。
三、在各个步骤中使用的编译命令
用下面的程序举例子
#include<stdio.h> #define MAX 100 int main() { /*I am only the test*/ printf("Hello World!"); printf("%d ",MAX); return 0; }
gcc -E hello.c -o hello.i //后可以看到宏定义和注释都没有了
gcc -S hello.i -o hello.s //后可以看到被编译成的汇编文件。
gcc -c hello.s -o hello.o //将汇编文件生成目标文件
以上三个步骤可以用一句话代替: gcc -c hello.c -o hello.o 这样可以直接生成目标文件。
最后链接成可执行文件有点麻烦:
ld -static /usr/lib/crt1.o /usr/lib/crti.o /usr/lib/gcc/i486-linux-gnu/4.1.3/crtbeginT.o -L/usr/lib/gcc/i486-linux-gnu/4.1.3 -L/usr/lib -L/lib hello.o --start-group -lgcc -lgcc_eh -lc --end-group /usr/lib/gcc/i486-linux-gnu/4.1.3/crtend.o /usr/lib/crtn.o
当然各个文件在不同系统中的路径也不一定相同,如我在centos6.6就是下面的结果:
ld -static /usr/lib64/crt1.o /usr/lib64/crti.o /usr/lib/gcc/x86_64-redhat-linux/4.4.4/crtbeginT.o -L/usr/lib/gcc/x86_64-redhat-linux/4.4.4 -L/usr/lib64 -L/lib64 hello.o --start-group -lgcc -lgcc_eh -lc --end-group /usr/lib/gcc/x86_64-redhat-linux/4.4.4/crtend.o /usr/lib64/crtn.o