此帖是记录视频内容,防止后续自己遗忘
一、预处理
1、库文件
预处理等于一个文本粘贴过程,编译后把库文件展开,展开本质也为粘贴内容
这里的#include
2、类函数宏
预处理阶段值进行文本粘贴,不会先求值。
小心优先级等问题
eg:#define max(a,b) ((a) > (b) ? (a) : (b ))
int main(){
max(i++,j++);
}
预处理阶段展开后就是 ((i++) > (j++) ? (i++) : (j++));
仅仅是进行文本粘贴
3、其他工作
二、编译
编译过程
词法分析->语法分析->语义分析->中间代码生成->优化->优化->目标代码生成
1、词法分析识别并记录源文件的token,such as 标识符 关键字 常数等,还包含了token位置信息(文件名行列号)
词法分析器本质上是字符串匹配程序(对代码进行匹配),所以C代码相当于一个字符串,词法分析器就是匹配而已
2、语法分析
按照C语言的语法将识别出来的token组织成树状结构
AST 抽象语法树,反映出源程序层次结构和语法错误。
3、有些工具(clang)可以进行静态程序分析,也就是语义分析,本质上就是对抽象语法树进行扫描,可以检查出语法错误,性能等问题。
4、中间代码生成
把C语言状态机翻译成IR状态机
中间代码的作用
本质上就是适应诸多类型的输入,诸多类型的代码经编译器转为中间代码,而中间代码IR可以针对ISA需求输出不同的ISA指令(我是这样理解的)
5、优化
简单来说优化的前提是符合中间三条,例如优化前scanf 和printf数据对应一致,优化后依旧一致,那么此次优化有效。
中间编译器怎么执行并不太关心,保证起始和结果一致就可以。
例如gcc -O1 一级优化,可以把指令数减少。
但是特殊的是volatile ,一旦变量前加入volatile后,编译器对该变量的访问和读写都需要严格执行。
例如上述代码,如果int z = x + y;在进行优化时编译器可能直接把z的值算出来,也就是直接进行赋值而不计算。但是添加volatile后严格执行读写指令,不进行优化
6、目标代码生成
将IR状态机翻译成ISA状态机。前面中间代码图中已经写出x86等ISA,这一步就是目标代码生成的过程。
eg:
main:
addi sp,sp,-32
sw ra,28(sp)
sw s0,24(sp)
addi s0,sp,32
......
优化后指令可能会变少,还有变量放到寄存器等优化方法。
生成汇编代码后
三、二进制
根据指令集手册,把汇编代码编译成二进制目标文件
我们用objdump可以把二进制文件通过反汇编变成可读的指令。
四、链接
合并多个目标文件,生成可执行文件。在合成可执行文件后,通过反汇编可以看到可执行文件实质上是多个目标文件指令的集合,包括地址,main函数等。所以如果多个目标文件合成一个可执行文件,也只能有一个main函数。
五、执行
main函数并不是程序执行的入口,先要加载到内存等等操作。
注意编译选项可能会影响程序输出或执行的行为,例如C标准 C99 /C90等。
例如assert(sizeof(long) == 4)
例如原来是32位,在移植64位环境后可能会出错。
未定义行为:
多次执行的结果都不一样。
所以定义的行为,要写到一个文档中,叫ABI ,这个ABR是 计算机系统软硬件协同的重要体现。
一个程序的运行与源代码,编译器 环境 OS 硬件都有关系。
具体如下:
六、对ABI数据类型进行定义
例如stdint ,int8_t,有符号数8位,即使在不同平台也可以通过stdint 来固定
同时不同平台,程序输出不同,
x86输出与riscv平台下输出不同。 当x84平台下 char为符号数的时候,0xff为255,printf(c == 0xff) 类似证明 -1 == 255,但是在riscv架构下 char 位unsign ,相当于直接进行比较 输出为T
summary
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构