网站层次索引模板,龙华营销型网站制作,河北省住房与城乡建设厅网站,便宜WordPress主机目录 Part 06 How the C Compiler Works
1.编译过程
2.C并不关心文件
3.翻译单元#xff08;Translation Unit#xff09;
4. 实际用代码感受一下编译过程
4.1 预处理
4.1.1 预处理的本质
4.1.2 预处理后的.i文件
4.1.3 骚操作
4.2 .asm文件#xff08;汇编语言源文…目录 Part 06 How the C Compiler Works
1.编译过程
2.C并不关心文件
3.翻译单元Translation Unit
4. 实际用代码感受一下编译过程
4.1 预处理
4.1.1 预处理的本质
4.1.2 预处理后的.i文件
4.1.3 骚操作
4.2 .asm文件汇编语言源文件
4.2.1 直观感受优化
4.2.2 常量折叠Constant Folding Part 06 How the C Compiler Works
1.编译过程
我们在编译软件上写的内容实际上就是文本那从文本到可执行的程序中间需要经过两步编译和链接。编译就是将文本转化为.obj后缀的目标文件。
编译又可以分为几个步骤
①预处理Preprocessing
②词法分析Lexical Analysi
③语法分析Syntax Analysis
④语义分析Semantic Analysis
⑤优化Optimization
⑥代码生成Code Generation
我们可以用一个简单的例子来直观感受一下每个过程
源代码
#include stdio.hint main() {int a 5;int b 7;int sum a b;printf(Sum is: %d\n, sum);return 0;
}词法分析生成词法单元Tokens
Keyword: include
Identifier: stdio.h
Keyword: int
Identifier: main
Punctuation: (
Punctuation: )
Punctuation: {
Keyword: int
Identifier: a
Operator:
Number: 5
Punctuation: ;
...语法分析生成抽象语法树AST
Program
|
└── Function: main├── Declaration: int a├── Assignment: a 5├── Declaration: int b├── Assignment: b 7├── Declaration: int sum├── Assignment: sum a b├── Function Call: printf│ ├── String: Sum is: %d\n│ ├── Argument: sum└── Return: 0语义分析
语义信息 类型检查通过变量和函数引用有效。符号表记录了变量的类型和位置。
优化Optimization:
int a 5;
int b 7;
int sum a b;
printf(Sum is: %d\n, sum);
return 0;代码生成(Code Generation)
MOV a, 5
MOV b, 7
ADD sum, a, b
PRINT Sum is: %d\n, sum
HALT转成obj机器码 2.C并不关心文件
这主要是跟Java做对比
在Java中一个源代码文件通常对应一个类且文件名与类名相同并以 .java 为扩展名。而在C中源代码可以分布在多个文件中每个文件独立地包含了一部分程序的实现。这些文件可以包含函数、类、变量的定义和实现等。默认情况下编译器看到.h文件会将其作为头文件处理见到.cpp文件会将其作为源文件处理。你可以改变它不具有强制性。 总而言之
在Java中文件名与类名的匹配是强制性的而且有严格的规范。这种规范有助于提高代码的可读性和可维护性但也限制了一定的灵活性。
在C中虽然有一些约定例如使用.cpp作为源文件的扩展名但这些并非强制性规定。C更加注重灵活性和兼容性允许开发者使用不同的扩展名或者甚至没有扩展名。这种灵活性允许开发者更自由地组织和命名源文件但也可能导致一些不规范的实践。 3.翻译单元Translation Unit
在编译过程中被处理的最小单元。在C中一个源文件和一个头文件通常就是一个翻译单元。一个翻译单元作为编译器的输入经过编译过程后生成一个目标文件然后多个目标文件可以被链接在一起形成最终的可执行程序。 4. 实际用代码感受一下编译过程
4.1 预处理
4.1.1 预处理的本质
预处理其实就是复制粘贴头文件的内容
我们写两个源文件感受一下
math.cpp(没有包含任何头文件)
int Multiply(int a, int b)
{return a b;
}
Log.cpp(包含iostream头文件)
#include iostreamvoid Log(const char* message)
{std::cout message std::endl;
}然后我们单独编译CtrlF7
得到两个obj文件 大小差距很大造成这种现象的原因就是包含头文件预处理会把头文件的内容全部复制到Log.cpp中来。
4.1.2 预处理后的.i文件
.i 文件是指预处理后的源文件。在C和C编译过程中预处理器会对源文件进行处理展开宏、处理条件编译指令等并生成一个经过预处理的中间文件。这个中间文件的扩展名通常是.i。
我们可以通过更改下面的设置来让他生成预处理后的.i文件 对Main.cpp文件预处理
#include iostreamvoid Log(const char* message);int main()
{Log(Hello World!);//用Log函数实现打印的功能std::cin.get();
}文本编辑器打开Main.i文件
我们会直观的发现这个现象 4.1.3 骚操作
利用这个特点我们可以实现一些骚操作
建一个只有一个结束大括号的头文件
EndBrace.h
}
然后将原本.cpp文件中的结束大括号改成#include “EndBrace.h”自己建立的头文件用双引号还能编译成功吗
Math.cpp(替换)
int Multiply(int a, int b)
{return a b;
#include EndBrace.h 成功
打开生成的.i文件 还可以利用宏定义
#define (被替换的内容) 替换后的内容
#define Age 18 就是在预处理的时候把代码里面的Age全部替换成18
Math.cpp(替换)
#define INTEGER int
INTEGER Multiply(INTEGER a, INTEGER b)
{return a b;
}成功
打开生成的.i文件 #if 预处理指令用于条件编译。它允许根据指定的条件来选择性地包含或排除部分代码。
Math.cpp(替换)
#if 1
int Multiply(int a, int b)
{return a b;
}
#endif打开生成的.i文件 Math.cpp(替换)
#if 0
int Multiply(int a, int b)
{return a b;
}
#endifv打开生成的.i文件 4.2 .asm文件汇编语言源文件
关闭刚才的设置 更改汇编语言文件输出 这里我们可以看到源代码的汇编语言格式 4.2.1 直观感受优化 Debug版本总共有253行
如果我们改一下Debug版本的设置为 这个时候我们继续运行编译 只有177行
4.2.2 常量折叠Constant Folding
常量折叠Constant Folding是编译器在编译时对表达式中的常量进行计算和简化的过程。在这个过程中编译器会尽可能地将表达式中的常量计算出结果以减少运行时的开销。
改一下Math.h
int Multiply()
{return 2*5;
}编译并查看生成的汇编文件.asm