编译器是将人类可读的源代码转换为计算机可执行的机器代码的程序,为了成功地做到这一点,人类可读的代码必须符合编写它的任何编程语言的语法规则。编译器只是一个程序,无法为您修复代码。如果你犯了一个错误,你必须纠正语法,否则它将无法编译。
编译代码时会发生什么?
编译器的复杂性取决于语言的语法以及编程语言提供的抽象程度。C 编译器比 C++ 或 C# 编译器简单得多。
词法分析
编译时,编译器首先从源代码文件中读取字符流,并生成词法标记流。例如,C++代码:
int C= (A*B)+10;
可以分析为以下令牌:
- 键入“int”
- 变量“C”
- 等于
- 左括号
- 变量“A”
- 次
- 变量“B”
- 右括号
- 加
- 字面“10”
句法分析
词法输出转到编译器的语法分析器部分,该部分使用语法规则来确定输入是否有效。除非变量A 和 B 之前已声明且在范围内,否则编译器可能会说:
- “A”:未声明的标识符。
如果它们已声明但未初始化。编译器发出警告:
- 未初始化使用的局部变量“A”。
切勿忽略编译器警告。它们可能会以奇怪和意想不到的方式破坏您的代码。始终修复编译器警告。
一通还是两通?
编写一些编程语言,以便编译器只能读取一次源代码并生成机器代码。就是这样一种语言。许多编译器至少需要两次传递。有时,这是因为函数或类的前向声明。
在C++中,可以声明类,但要到以后才能定义。编译器在编译类的主体之前无法计算出类需要多少内存。在生成正确的机器代码之前,它必须重新读取源代码。
生成机器代码
假设编译器成功完成了词汇和语法分析,最后阶段是生成机器代码。这是一个复杂的过程,尤其是对于现代 CPU。
编译可执行代码的速度应尽可能快,并且可以根据生成的代码的质量和请求的优化量而有很大差异。
大多数编译器都允许您指定优化量 — 通常以快速调试编译和对已发布代码进行完全优化而闻名。
代码生成具有挑战性
编译器编写器在编写代码生成器时面临挑战。许多处理器通过使用
- 指令流水线
- 内部缓存。
如果代码循环中的所有指令都可以保存在 CPU 缓存中,那么该循环的运行速度比 CPU必须从主 RAM 获取指令时快得多。CPU 缓存是内置于 CPU 芯片中的内存块,其访问速度比主 RAM 中的数据快得多。
缓存和队列
大多数 CPU 都有一个预取队列,CPU 在执行指令之前将指令读入缓存。如果发生条件分支,CPU 必须重新加载队列。应生成代码以最大程度地减少这种情况。
许多 CPU 都有单独的部件,用于:
- 整数算术(整数)
- 浮点运算(小数)
这些操作通常可以并行运行以提高速度。
编译器通常将机器代码生成到目标文件中,然后通过链接器程序链接在一起。