文章目录
C++编译器的工作原理
window下编译器只负责将源文件转换为中继格式obj
1.预处理
所有的preprocessor语句将会在这个阶段进行评估
2.tokenizing and parsing阶段(具体参考编译原理)
创建抽象语法分析树
3.本质
编译器的工作将代码转化为 constant data(常数资料) or instruction(指令)
其实从编译原理的角度就是 语法树和符号表
特性
和java相比,C++不关心文件,java类名和文件名必须相同,文件结构也要和package相同,而C++文件只是提供源码的方法,文件没有任何意义,只是由后缀名告诉编译器这是.c要按c编译,这是.h头文件,你也可以用一个没有任何意义的后缀,只要你在代码中告诉编译器请按cpp进行编译
头文件的工作例子
我们新建一个EndBrace.h
内容仅仅为一个右括号
我们删除log.cpp的右括号,编译失败了
我们修改为下面的样子
#include <iostream>
void log(const char* message)
{
std::cout << message << std::endl;
#include "EndBrace.h"
编译成功,可见头文件仅仅起到一个内容复制粘贴的作用
为了更加形象,我们设置预处理到文件,看看究竟发生了什么
为了简单我新建了一个math.cpp(不包含iostream,因为你会发现它足足有5万多行)
内容如下
void math(const char* message)
{
#include "EndBrace.h"
预编译生成的.i文件内容如下
可见预处理后仅仅将复制过来而已
至于#line语句仅仅是改变行数和文件名而已可以不理会
#define 的工作(宏定义:替换)
#define INTEGER int
INTEGER math(const char* message)
{
INTEGER x = 0;
return x;
}
编译后查看.i文件
#line 1 "C:\\Users\\Aimer\\source\\repos\\class2.2\\class2.2\\math.cpp"
int math(const char* message)
{
int x = 0;
return x;
}
仅仅将INTEGER替换为 int
#if的工作
#define INTEGER int
#if 1
INTEGER math(const char* message)
{
INTEGER x = 0;
return x;
}
#else
void math(const char* message)
{
INTEGER x = 0;
}
#endif
编译后查看.i文件
#if 后面为真(非0)则注释掉#else后的语句块
否则注释掉#if后的语句块
#if对于注释掉大块语句与/**/相比有着十分方便的使用
你只需要修改#if 后的条件即可方便的选择使用或注释掉某语句块
和if else的不同:
if else会直接编译执行,而#if是编译器指令,其作用是告诉编译器,有些语句行希望在条件满足时才编译。
.obj文件里是什么
取消预处理到文件,重新编译,查看math.obj
可见是大量的机器码,显然机器码对于人类而言可读性几乎没有,我们想办法转为可读性强一点的汇编代码
编译查看
编译器优化
选择最大优化
编译错误
设置基本运行检查为默认
编译后生成的汇编代码量明显减少
我们修改代码没有任何输入,单纯计算两个常量的和
int math()
{
return 5+8;
}
查看汇编代码
发现没有进行加操作,仅仅只是将13push
这涉及到了常量折叠(constant folding),没有必要在运行时去计算两个常量的和,在编译阶段就已经计算完成,是编译优化的一部分
关于常量折叠的详情可以点击上方超链接,这里就不详细讲解。
我们再测试一个例子
const char* log(const char* message)
{
return message;
}
int math()
{
log("hello");
return 5+8;
}
log只是返回了message实际上什么也没干
无优化下的汇编代码
?log@@YAPBDPBD@Z涉及函数签名来独一无二地定义你的函数,我们在linker章节再讲,linker会通过函数签名联系多个obj之间的函数
我们开启最大优化速度
call语句完全消失了,因为编译器检测到我们的函数实际上什么也没干,直接就优化掉了。