编译器对源代码的编译过程

编译器对源代码的编译过程

对于C/C++源代码的编译,可以使用gcc(GNU Compiler Collection,GNU编译器集合)/g++进行编译。
gcc/g++分别是GNU的C/C++编译器,GNU是“GNU is Not Unix”的首字母缩写,GNU项目Richard Stallman在1983年9月27日公开发起的,GNU软件可以自由使用、复制、修改和发布,由此产生的GNU通用公共许可证(GNU General Public License,GPL)。
假设我们有一个源文件:hello_world.cpp(或者hello_world.c)。
我们知道,C/C++程序的执行分为四个步骤:
- 预处理
- 编译
- 汇编
- 链接

1.预处理阶段

例如:g++ -E hello_world.cpp -o hello_world.i
-o选项表示输出;使用-E选项,由预处理器cpp将源代码生成预处理.i文件。
预处理会展开以#开始的行,解释为预处理指令,包括:

(1). #include:头文件包含

#include <xxx.h>使用尖括号通常表示引用系统的标准库头文件,而且搜索路径也只在标准库路径;
#include "xxx.h"使用双引号通常表示引用自定义头文件,搜索路径是先在当前工程目录下搜索,搜索不到再到系统的标准库中搜索。

(2). 宏替换

无参数宏和带参数宏;多行宏(用\连接,\后面不能右空格);终止宏定义的作用域;宏替换(使用##,字符串替换)

#define PI 3.1415926    //无参数
#define MAX(A,B) ((A)>(B)?(A):(B))  //注意只作简单替换,不进行类型检查,所以对每一个参数加了括号
#define MIN(A,B)    \
    ((A)<(B)?(A):(B))   //多行宏
#undef PI   //终止宏PI的定义
#define B(name) my_##_name   //B(first)替换为my_first_name

(3).条件编译指令

#if     //表达式为零对代码编译
#else
#elif
#ifndef //宏没有被定义就进行编译
#ifdef  //宏被定义就进行编译
#endif  //结束宏定义

例如:防御式头文件声明,防止重复引用:

#ifndef  XXX_H_
#define  XXX_H_
... 
#endif  //XXX_H_

(4). 预定义的宏

__DATE__        //字符串常量,表示编译日期,形式:mm dd yyyy
__TIME__        //字符串常量,表示编译日期,形式:hh:mm:ss
__FILE__        //字符串常量,表示源文件(包含文件路径)
__FUNCTION__    //字符串常量,表示当前函数名
__LINE__        //整数常量,表示源文件行号

(5). 其它一些常见的预处理指令

#pragma once:保证头文件编译一次;
#Pragma pack(x):规定结构体/类等的对齐长度,x是一个数字

2.编译阶段

编译阶段由预处理器将预处理.i文件转换为汇编代码.s文件,例如:

g++ -S hello_world.cpp -o hello_world.s

-S选项只激活了预处理和编译,从源文件.cpp到汇编文件.S

3. 汇编阶段

汇编阶段由汇编器as将汇编代码.S文件转化为机器代码.o文件,例如:

g++ -c hello_world.cpp -o hello_world.o

-c选项激活了预处理、编译和汇编,从源文件.cpp到目标机器文件.o

4. 链接阶段

链接阶段由链接器ld将机器代码.o文件连接生成可执行文件.exe/.out,例如:

g++ hello_world.o -o hello_world.out

链接的过程核心工作是解决各个模块间的符号(变量)相互引用的问题。
静态链接和动态链接:
- 静态链接
将函数代码直接拷贝到可执行程序中,如果多个程序调用同一个函数,内存中就会存在多个拷贝,不仅浪费了内存空间,还增大了可执行文件的体积。
- 动态链接
动态链接相比较于静态链接,并没有直接拷贝,只是在可执行文件中添加了一些重定位信息,当可执行文件真正执行时,动态链接库的内容才被映射到相应的进程,这时可执行文件根据定位信息找到相应的函数代码执行,这样节约了内存空间。

5. 编译器的一些默认操作

g++ -E hello_world.cpp      //没有生成.i文件,直接输出.i文件
g++ -E hello_world.cpp -o test.i
g++ -E hello_world.cpp > test.i     //正确的做法

g++ -S hello_world.cpp  //默认生成hello_world.s
g++ -S hello_world.cpp -o hello_world.s     //等价

g++ -c hello_world.cpp  //默认生成hello_world.o
g++ -c hello_world.cpp -o hello_world.o     //等价
g++ -c hello_world.s -o hello_world.o       //等价
g++ -c hello_world.i -o hello_world.o   //这个是错误的,不能对.i执行-c选项

g++ hello_world.cpp     //默认直接生成a.out文件
g++ hello_world.cpp -o hello_world.out
g++ hello_world.o -o hello_world.out    //等价
g++ hello_world.s -o hello_world.out    //等价
g++ hello_world.i -o hello_world.out    //这个是错误的,不能对.i执行

6.常见的命令选项

-E, -s, -c, -o      //这四个是前面讲过的
-O0         //编译器不进行优化处理
-O/-O1      //编译器优化编译时间和可执行文件大小,缺省
-O2         //在-O1的基础上进一步优化
-O3         //在-O2的基础上进一步优化,包括inline函数

更加具体的请参考官方文档

gcc.pdf以及中文手册GCC_zh.htm

猜你喜欢

转载自blog.csdn.net/qq_25467397/article/details/81480113