源代码到可执行程序的过程详解:预编译、编译、汇编、链接

1、gcc编译过程分解

源代码到可执行程序的过程详解:预编译、编译、汇编、链接_第1张图片

(1)首先是将.c源文件和.h头文件经过预编译(cpp是预编译器),得到.i文件,主要是进行的一些替换工作;
(2)将.i文件经过编译器(gcc)处理,得到.s汇编文件,现在文件内容已经从C语言编程了汇编语言;
(3).s汇编文件经过汇编器(as)处理变成.o文件,此时的.o文件已经是二进制文件;
(4)最后将所有.o文件和依赖的静态库、动态库通过链接器(ld)生成可执行程序a.out;

2、预处理

//预编译得到.i文件的指令
gcc -E hello.c -o hello.i
//或者
cpp hello.c > hello.i

预编译主要处理源代码文件中以"#“开头的预编译指令,比如”#include",主要规则如下:
(1)将所有的"#define"删除,并且展开所有的宏定义;
(2)处理所有的条件编译指令,比如"#if"、“#ifdef”、“#elif”、“#else”、“#endif”;
(3)处理"#include"预编译指令,将被包含的头文件插入到该预编译指令的位置,注意这个过程是递归进行的,也就是头文件内部还可以包含头文件;
(4)删除所有的注释,就是用"//“和”/* */开头的";
(5)添加行号和文件名,以便在编译、运行时在显示报错信息、警告信息、调试信息时可以显示行号和文件名;
(6)保留所有#pragma编译器指令,因为编译器要使用他们;
总结:预编译后不再有宏定义、头文件、条件编译等,一切都是确定的代码;我们可以用预编译来判断宏定义是否正确、头文件是否包含正确等;

3、编译

//编译得到.s文件的指令
gcc -S hello.i -o hello.s
//或者
gcc -S hello.c -o hello.s

源代码到可执行程序的过程详解:预编译、编译、汇编、链接_第2张图片

编译的作用是实现高级语言到汇编语言的转换,详细步骤如下:
(1)扫描:主要是进行词法分析,将源代码看做一个一个的记号,然后将记号进行分类,一般分为如下几类:关键字、标识符、字面量(数字、字符串等)、特殊符号(加号、等号等);
(2)语法分析:语法分析会生成语法树,上一步的扫描只是对单个符号进行处理,而语法分析则是要将各个符号结合起来分析,形成表达式。比如:语法分析需要处理运算符的优先级问题(比如乘法的优先级高于加法);"*"在C语言中就可以表示乘法也可以表示指针解引用,语法分析就需要对此作出判断;
(3)语义分析:语法分析解决了代码之间怎么结合的问题,语义分析需要判断这样结合是否有意义,语法正确并不代表语义上也正确。比如:对两个指针变量做乘法运算是没有意义但符合语法;不同类型之间的变量做强制转换时,涉及的隐式转换就是语义分析阶段处理的;将int型变量赋值给指针变量,编译时报类型不匹配也是语义分析阶段处理的;
(4)源代码优化:在源代码级别进行优化处理;比如:代码中有"2+6"这样的表达式,在优化后直接变成8。实际的代码优化过程比较复杂,还涉及中间代码的生成,这里就不再做分析;
(5)代码生成和目标代码优化:将上一步产生的中间代码转换成目标机器代码,这是和具体的目标CPU相关的;

4、汇编

gcc -c hello.c -o hello.o
或者
as hello.s -o hello.o

(1)汇编的作用:将汇编语言翻译成机器能识别的二进制;
(2)将每一句汇编语言翻译成机器指令(二进制指令),可以理解成汇编指令和机器指令有对照表;
(3)汇编语言是和具体的CPU硬件关联。同样的代码,不同架构CPU的汇编器最终得到的二进制是不同的,这也是为什么嵌入式开发中,需要用CPU对应的交叉编译工具链去编译可执行程序的原因;

5、链接

源代码到可执行程序的过程详解:预编译、编译、汇编、链接_第3张图片

(1)在经过上述步骤后生成了.o文件,到目前为止都是以单个文件来处理的,文件之间或者是库之间的调用关系还没有处理;
(2)我们把链接过程理解成拼图的过程。比如:A模块引用了B模块的fun函数,在编译A模块时检测到调用了fun函数,但是A模块中并没有发现fun函数的实现,于是就会在调用fun函数的地方做个标记(相当于缺了一块),当链接的时候就去所有模块里查找是否有fun函数的实现;恰好在B模块中找到fun函数的实现(如果在多个模块找到fun函数定义就报重复定义的错误),于是就把B模块fun函数和A模块调用的地方链接起来;
总结:链接过程就是把所有.o文件根据函数调用关系拼接在一起,形成可执行程序;

你可能感兴趣的:(#,《程序员的自我修养》,c++,c语言,linux,编译链接)