
C编译器剖析
文章平均质量分 73
SheIsC
这个作者很懒,什么都没留下…
展开
-
C编译器剖析_Github
欢迎前往GitHub下载和修改UCC编译器的相关代码,和配套的清晰版的PDF文档。UCC编译器遵循C89标准,代码在1万多行。只要努力,这是一个C程序员单枪匹马就可掌控的。在这里,可从C编译器实现的高度来重新认识C语言。https://github.com/sheisc/ucc162.3原创 2016-12-02 19:36:29 · 5674 阅读 · 1 评论 -
C编译器剖析_5.2.3 中间代码生成及优化_通过“偏移”访问数组元素和结构体成员
第5.2.3节 通过“偏移”访问数组元素和结构体成员 在上一节小节,我们举例介绍了对“数组元素和结构体成员”的访问,我们采用的是“基地址+偏移”的模式来计算其内存单元的地址。对于数组元素arr2[i][2]来说,数组索引值i为变量,对应的地址要表达为“基地址+常量偏移+非常量偏移”;对于结构体成员dt.b来说,其地址可表达为“基地址+常量偏移”。下面,我们还是结合一个简单的例子来说明相原创 2015-04-15 20:31:18 · 1817 阅读 · 0 评论 -
C编译器剖析_5.2.2 中间代码生成及优化_再论符号symbol与公共子表达式
5.2.2 再论符号symbol与公共子表达式 在介绍算术表达式的翻译前,让我们简单重温一下第2.5节中的“图2.5.4 公共子表达式”及“图2.5.5 valueDef和valueUse”。为阅读方便,我们再次给出这两张图,更详细的说明请参见第2.5节。对于图2.5.4第2行的a+b,我们会由第7行的中间代码来对a+b进行求值,其结果存于临时变量t1中,之后在第3行中再次遇到表达原创 2015-04-12 19:23:04 · 2042 阅读 · 0 评论 -
C编译器剖析_5.3.2 中间代码生成及优化_switch语句的翻译
5.3.2.Switch语句的翻译 在这一小节中,我们来讨论一下switch语句的翻译,switch语句的产生式如下所示。SwitchStatement: switch( expr ) statement 当C程序员编写出如下代码时,UCC编译器会在语义检查阶段进行报错“error:The break shall appear in原创 2015-04-22 17:04:42 · 2021 阅读 · 0 评论 -
C编译器剖析_5.2.1 中间代码生成及优化_布尔表达式的翻译
5.2 中间代码生成与优化_布尔表达式的翻译 我们仍然按照语法分析和语义检查时的思路,先讨论表达式的翻译,再处理语句。表达式从概念上来说,可分为算术表达式和布尔表达式,在一些编程语言(例如Java)中对这两者是有严格区分的,算术表达式的结果是整数或浮点数,而布尔表达式的结果是逻辑上的真或假。布尔是英国数学家,由于布尔较早进行了关于“与或非”逻辑运算的研究,为了纪念这位先驱,在Java中原创 2015-04-10 14:26:20 · 2842 阅读 · 0 评论 -
C编译器剖析_5.1 中间代码生成及优化_简介
本节对UCC编译器的中间代码生成及优化进行简介,给出基本块BasicBlock、三地址码、控制流图CFG的相应数据结构,介绍有条件跳转、无条件跳转和间接跳转等概念。原创 2015-04-08 19:41:24 · 4751 阅读 · 0 评论 -
C编译器剖析_5.2.5 中间代码生成及优化_赋值表达式的翻译
5.2.5 赋值表达式的翻译 在这一小节中,我们来讨论一下赋值表达式的翻译,例如“a=a-b;”或者“a += b;”等。在图5.2.13的下方我们给出了表达式“a+=b;”在语义检查前后的语法树,右下侧的语法树相当于是表达式a = a’+b,但表达式中的a和a’对应同一个语法树结点。按C的语义,语句“a+=b;”中的a只能被求值一次,而不能求值两次。例如,若a结点对应一个函数调用(*f原创 2015-04-18 17:53:20 · 1171 阅读 · 0 评论 -
C编译器剖析_4.2 语义检查_表达式的语义检查(6)_一元运算符表达式
在这一小节中,我们来讨论一元运算符表达式的语义检查,与其相关的代码如图4.2.35所示。对于“前加加”和“前减减”运算符而言,我们采取的策略跟处理“后加加”和“后减减”一样,都是将--a转换为a -= 1,而将++a转换为a += 1,所以图4.2.35第5行调用的函数,就是我们在讨论后缀表达式语义检查时介绍过的函数TransformIncrement()。对于形如+a或-a的表达式,我们需要检查原创 2015-03-14 17:42:46 · 1227 阅读 · 0 评论 -
C编译器剖析_C类型系统_相容类型Compatible Type
在前文对函数调用进行语义检查时,我们用函数CanAssign()来判断“能否能把实参赋值给形参”,在函数CanAssign中,我们又用宏IsCompatiblePtr来判断两个指针是否相容。而如果指针变量T1 * ptr1和T2 * pt2相容,则意味着类型T1和T2是相容的。UCC编译器中ucl\type.c的函数IsCompatibleType()用于判断类型是否相容,与类型系统相关的数据结构原创 2015-03-10 20:28:12 · 2001 阅读 · 0 评论 -
C编译器剖析_4.4 语义检查_外部声明_类型结构的构建(2)
在这一小节中,我们将对形如第3章图3.3.17所示的结构体语法树进行语义检查,从而构建结构体的类型结构。 图3.3.17 ParseStructOrUnionSpecifier()构建的语法树 我们在第2章中给出了以下结构体struct Data对应的类型结构,如图2.4.4所示。为了阅读方便,我们重新给出这2幅图,由图示我们也能较清楚地预览本节的起点和终点。原创 2015-03-20 14:24:39 · 1410 阅读 · 0 评论 -
C编译器剖析_4.2 语义检查_表达式的语义检查(4)_函数调用
4.2.4 函数调用的语义检查 在这一小节中,我们来讨论一下函数调用的语义检查,语法上,函数调用对应的表达式属于后缀表达式PostfixExpression,UCC编译器exprchk.c的函数CheckFunctionCall()完成了对函数调用的语义检查,如图4.2.18所示。在阅读这份代码时,需要对语法分析后为函数调用构造的语法树有较好认识,请先参照”图3.1.21后缀运算符对应原创 2015-03-06 15:15:15 · 1975 阅读 · 0 评论 -
C编译器剖析_4.2 语义检查_表达式的语义检查(3)_字符串与标识符
4.2.3 在这一小节,我们先来分析一下基本表达式PrimaryExpression的语义检查,由C的标准文法,我们可以知道与PrimaryExpression相关的产生式如下所示,即加了一对小括号的表达式(Expression)在语法上也相当于标志符ID、常量CONST和字符串StringLiteral。primary-expression:原创 2015-03-03 11:29:26 · 1875 阅读 · 0 评论 -
C编译器剖析_4.2 语义检查_表达式的语义检查(5)_结构体成员选择
4.2.5 成员选择运算符 在C语言中,结构体struct和联合体union被称为记录类型RecordType,在形如dt.a和ptr->a的后缀表达式中,运算符.和->被称为成员选择运算符。函数CheckMemberAccess()用于对这些表达式进行语义检查,与之相关的代码如图4.2.28所示。在表达式dt.a中,dt和a相当于是运算符.的两个操作数,dt对应的语法原创 2015-03-07 14:05:18 · 1616 阅读 · 0 评论 -
C编译器剖析_4.4 语义检查_外部声明_内部连接和外部连接
本节介绍C语言的内部连接Interal Linkage和外部连接External Linkage。原创 2015-03-26 16:31:20 · 1263 阅读 · 0 评论 -
C编译器剖析_5.4.2 中间代码生成及优化_基本块的合并
5.4.2 基本块的合并 我们在第5.4.1节时给出了由基本块构成的双向链表和控制流图,为阅读方便,我们这里再次给出“图5.1.4 基本块的静态结构和动态结构”。在这一小节中,我们试图把双向链表中相邻的基本块进行合并,当然这种合并需要满足一定条件,同时要保持程序的原有语义。在合并后,控制流图中的前驱与后继关系也要进行调整。我们需要改动的数据结构有图5.4.1中的双向链表和控制流图。需要原创 2015-04-24 17:05:20 · 3643 阅读 · 0 评论 -
C编译器剖析_5.3.1 中间代码生成及优化_If语句和复合语句的翻译
5.3.1 If语句和复合语句的翻译 我们先简单回顾一下对布尔表达式的翻译,我们通过调用TranslateBranch函数来产生跳转指令,从而实现布尔表达式的语义。在使用函数TranslateBranch(expr, bt, bn)时,有这么两个约定: (1) 当expr为真时,跳往bt基本块; (2) 紧随“函数TranslateBranch所生成的跳转指令”之原创 2015-04-20 15:48:35 · 1949 阅读 · 0 评论 -
C编译器剖析_5.2.4 中间代码生成及优化_后缀表达式的翻译
5.2.4 后缀表达式的翻译 在前面的章节中,我们介绍了用于对数组元素和结构体成员进行访问的函数Offset,其接口如下所示,参数addr代表了基地址,参数voff代表可变偏移,而参数coff则代表常量偏移。 Symbol Offset(Type ty, Symbol addr,Symbol voff, int coff); 函数Offset的基本想法是产生原创 2015-04-17 00:01:20 · 1457 阅读 · 0 评论 -
C编译器剖析_源代码说明
C编译器剖析_源代码说明原创 2015-01-04 17:28:35 · 5745 阅读 · 0 评论 -
C编译器剖析PDF文档及UCC编译器162.3
为方便大家的阅读,我把博客从2015年1月初至5月初的发布几十篇文章整理成一份PDF文档,需要的朋友可前往http://download.youkuaiyun.com/detail/sheisc/8669715下载。 书中格式不规范之处,请多多见谅。例如,代码贴图的风格并不太统一,前几章的贴图是直接截取SourceInsight的界面,行号就是在源代码文件中的行号,这么做的初衷是为了便于能快速找原创 2015-05-06 21:46:09 · 4359 阅读 · 0 评论 -
C编译器剖析_6.3.5 汇编代码生成_为类型转换产生汇编代码
6.3.5 为类型转换产生汇编代码 在这一小节中,我们来讨论一下整型和浮点型之间的类型转换。有些类型转换并不需要在汇编层次进行数据转换,例如int和unsigned int之间的转换只是改变了表达式的类型,对数据本身并无影响,以下表达式“(unsigned int) a”对应的二进制数据为0xFFFFFFFF,而表达式“a”对应的二进制数据也为0xFFFFFFFF。但对相同内容的二进原创 2015-05-01 22:55:21 · 1953 阅读 · 0 评论 -
C编译器剖析_6.3.4 汇编代码生成_为函数调用与返回产生汇编代码
6.3.4 为函数调用与返回产生汇编代码 在这一小节中,我们来讨论一下如何为函数调用和函数返回生成汇编代码。函数调用对应的中间指令如下所示: //中间指令的四元式: 让我们先熟悉一下C函数的调用约定CallingConvention,我们需要把参数从右向左入栈(即从argn到arg1依次入栈),不妨记这些参数所占用的总内原创 2015-05-01 00:33:54 · 1794 阅读 · 0 评论 -
C编译器剖析_5.2.6 中间代码生成及优化_一元表达式及其他表达式的翻译
5.2.6 一元表达式及其他表达式的翻译 在这一小节中,我们先来讨论一下一元表达式的翻译,我们先举个例子来说明一下。在以下C程序中,符号arr被声明为int[3][5]的数组类型,UCC编译器在语义检查后,为表达式“**arr=30;”构造的语法树为“(= ([]([] arr 0) 0) 30)”,在中间代码生成阶段,对应的中间代码为“arr[0] = 30;”。由这个例子可见,原创 2015-04-19 21:33:33 · 1212 阅读 · 0 评论 -
C编译器剖析_6.3.3 汇编代码生成_为跳转指令产生汇编代码
6.3.3 为跳转指令产生汇编代码 在这一小节中,我们要为“有条件跳转”、“无条件跳转”和“间接跳转”产生相应的汇编指令。中间指令的四元式如下所示: (1) 有条件跳转,例如“if (a ////////对应的汇编代码////////// movl a, %ea原创 2015-04-30 17:08:29 · 2210 阅读 · 0 评论 -
C编译器剖析_6.3.2 汇编代码生成_为算术运算产生汇编代码
6.3.2 由EmitAssign函数产生算术运算的汇编代码 在这一小节中,我们要讨论的中间指令形如“t1: a+b;”或者“t2:&number”,这些指令用于进行一元或二元算术运算,并把运算结果保存在临时变量t1或者t2中。UCC中间指令的格式如下所示: // t1: a+b; // t2: &原创 2015-04-30 13:22:02 · 1348 阅读 · 0 评论 -
C编译器剖析_6.3.6 汇编代码生成_为“取地址”产生汇编指令
6.3.6 为“取地址”产生汇编指令 在这一小节中,我们来讨论一下以下两条中间指令的翻译: (1)取地址指令 例如 ,表示取number的地址并保存到临时变量t0中 (2)对象清零指令 例如,表示把arr所占16字节的内存清零 我们先举一个例子来说明,对于图6.3.14第4行局部数组arr的初原创 2015-05-02 21:14:43 · 1859 阅读 · 0 评论 -
C编译器剖析_尾声
尾声 总有曲终人散时,不知不觉我们已经完成了对UCC编译器的剖析,一路走来,最深的体会仍然是“纸上得来终觉浅,绝知此事要躬行”。按这个道理,理解UCC编译器的最好办法应是“直接阅读其源代码,思考UCC编译器在不同的执行点应处于怎样的状态,加入一些打印语句,输出相应的调试信息来验证自己的判断是否正确,如果发现Bug,就写一些测试程序来触发Bug,然后原创 2015-05-02 23:02:29 · 1699 阅读 · 0 评论 -
C编译器剖析_5.4.1 中间代码生成与优化_删除无用的临时变量和优化跳转目标
5.4.1 删除无用的临时变量和优化跳转目标 UCC编译器在优化方面做的工作不多,其中与优化有关的函数主要有以下几个:(1) Symbol Simplify(Type ty, int opcode, Symbol src1,Symbol src2);用于进行“代数恒等式”的简化,例如表达式“a(2) Symbol TryAddValue(Type ty,原创 2015-04-23 16:55:02 · 2017 阅读 · 0 评论 -
C编译器剖析_6.3.1 汇编代码生成_由中间指令产生汇编代码的主要流程
6.3.1 由中间指令产生汇编代码的主要流程 在这一小节,我们可把关注的焦点放在“如何把某条中间代码翻译成汇编代码”上。UCC编译器的中间代码是如下所示的四元式,包括运算符和3个操作数。 当然有些中间代码只需要用到opcode和DST就可以了,例如,无条件跳转指令“goto BB2;”就不需要SRC1和SRC2。为了便于汇编代码的生成,UCC编译器在u原创 2015-04-29 17:54:19 · 3226 阅读 · 0 评论 -
C编译器剖析_6.1 汇编代码生成_简介
6.1 汇编代码生成简介 历经词法分析、语法分析、语义检查和中间代码生成阶段,我们终于来到了“目标代码生成阶段”,由于UCC编译器的目标代码即为32位x86汇编代码,因此我们就把本章称为“汇编代码生成”。UCC编译器中的大部分源代码都适用于Windows和Linux平台,但Windows平台上缺省的汇编器支持Intel风格的x86汇编代码,而Linux平台默认的汇编器则采用AT&T风格的原创 2015-04-25 19:24:12 · 2660 阅读 · 0 评论 -
C编译器剖析_6.2 汇编代码生成_寄存器的管理
在计算机中,CPU的速度比内存的速度快得多,编译器应尽量有效地利用寄存器资源,减少对内存的不必要访问,从而提高由编译器生成的汇编代码的运行速度。在中间代码生成阶段,UCC编译器用临时变量t来存放形如“t: a+b;”的公共子表达式的值;到了汇编代码生成时,UCC编译器会尽可能地把这些公共子表达式的值存放在寄存器,当需要再次重用时,就可以直接由相应的寄存器中得到。不过,CPU中寄存器的资源是很有限的原创 2015-04-26 22:04:09 · 1575 阅读 · 0 评论 -
C编译器剖析_4.4 语义检查_外部声明_临门一脚
4.4.5 对外部声明进行语义检查的临门一脚 在前面几小节的基础上,我们基本上已经把球从后场带到对方球门前了,就差临门一脚了。在这一节中,我们来分析一下对全局变量进行语义检查的函数CheckGlobalDeclaration,和对函数定义进行语义检查的函数CheckFunction。对全局变量进行检查的主要代码如图4.4.23所示,我们省略了一些细节。图4.4.23第7行的C原创 2015-03-26 20:09:14 · 1437 阅读 · 0 评论 -
C编译器剖析_4.4 语义检查_外部声明_结构体和数组的初始化
本节讨论标量类型、数组、结构体和联合体的初始化。在C语言中,全局或静态变量的初始化应为常量;而局部变量没有此要求。原创 2015-03-26 11:00:16 · 1509 阅读 · 0 评论 -
C编译器剖析_C语言的变参函数
C语言的变参函数 UCC编译器中有不少地方使用了C语言的变参函数,这里我们专门用一小节来对C语言变参函数的实现原理进行分析。C标准库中的printf函数就是一个典型的变参函数,其接口如下所示,函数声明中的省略号…表明这是一个变参函数。 int printf(const char *format, ...); 下面我们举一个简单的例子来说明printf函数的调原创 2015-03-03 20:29:57 · 2013 阅读 · 0 评论 -
C编译器剖析_3.2 语法分析_C语言的语句
3.2 C语言的语句_Statement 在这一节中,我们对C语言的各种语句进行分析,相关代码在ucl\stmt.c中。结构体struct astNode是语法树所有结点的父类,而if语句等需要在语法树结点上记录更多的信息,所以在ucl\stmt.h中定义了相关的结构体,我们会在讨论具体的语句时,在语法树示意图上画出相应的结构体。图3.2.1给出了语句的分析函数ParseStateme原创 2015-02-02 17:55:39 · 1708 阅读 · 0 评论 -
C编译器剖析_1.5 结合C语言来学汇编
1.5 结合C语言来学汇编 汇编语言的语法和语义都不复杂,如果你会C语言,那你就一定能在很短时间内看懂本节介绍的汇编语言。但是要很熟练地阅读和编写汇编程序,则需要长期的训练。上世纪80年代末,求伯君闭关一年多,单枪匹马用洋洋洒洒数万行汇编代码开发出DOS版WPS,由此奠定江湖大佬地位,但时至今日,这样的个人英雄主义时代已经一去不复还。除了信息安全和软件逆向分析等少数领域外,需要使用汇原创 2015-01-15 20:28:55 · 2003 阅读 · 0 评论 -
C编译器剖析_1.5 结合C语言来学汇编_整数运算
让我们还是从熟悉的加减乘除等算术运算入手。图1.23给出了一个简单的C程序,其中包含了常见的C语言算术运算,我们依旧采取对比C语言代码和UCC编译器生成的汇编代码的方法来讨论。图1.23 arith.c 我们有意在图1.23的第1至4行定义了几个初始化或未初始化的变量。图1.24给出了这几个变量在汇编代码中的区别。在图1.24中,第7行至第9行对应的是初始化为10的全原创 2015-01-16 19:41:44 · 1504 阅读 · 0 评论 -
C编译器剖析_1.4 UCC编译器预览_UCC驱动
在上一小节,通过以下四条命令,我们有意地给ucc传递不同的参数来依次调用预处理器、编译器、汇编器和连接器。当然实际使用ucc命令时,我们不会绕这么大一个圈子,直接用”ucc hello .c -o hello”即可得到可执行程序hello。iron@ubuntu:demo$ ucc -E hello.c -o hello.iiron@ubuntu:demo$ ucc --dump-a原创 2015-01-13 15:33:32 · 2735 阅读 · 0 评论 -
C编译器剖析_3.1 语法分析_C语言的表达式(2)
接下来,我们来分析一下一元运算表达式,对应的分析函数ParseUnaryExpression()在ucl\type.c中,如图3.1.13所示。由第235至240行的产生式,我们能看到非终结符UnaryExpression有多个侯选式,第254至269行用来处理第237行的侯选式”UnaryOperator UnaryExpression”。对于表达式”*ptr++”而言,由第236行和第237原创 2015-01-31 19:58:13 · 1978 阅读 · 0 评论 -
C编译器剖析_1.4 UCC编译器预览_UCC的使用
1.4.1 UCC的使用 通过第1.3节的例子ucc\examples\sc,我们对如何根据语言的文法来编写语法分析器,建立语法树,之后在语法树的基础上生成中间代码有了一个感性的直观认识。当然,光有这些中间代码,C程序员还不能得到其所要的计算结果。C编译器还要由中间代码产生汇编代码。在剖析UCC编译器前,让我们先熟悉一下UCC编译器的使用。UCC编译器的大部分代码都是用标准C语言并调用原创 2015-01-12 12:02:59 · 4299 阅读 · 0 评论 -
C编译器剖析_3.1 语法分析_C语言的表达式(1)
3.1 C语言的表达式 从第3章开始,我们进入UCC编译器的语法分析阶段。相关的代码主要在ucl\decl.c、ucl\expr.c和ucl\stmt.c,分别对应声明Declaration、表达式Expression和语句Statement。在第一章时,我们已经通过一个简单的例子ucc\examples\sc对这些概念有了一个直观的体会,在这一章中,我们需要结合IT大佬们制定的C标准原创 2015-01-30 20:48:06 · 2854 阅读 · 0 评论