编译器对源代码的编译过程
对于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函数