C编译器剖析_4.2 语义检查_表达式的语义检查(7)_二元运算符_赋值运算_条件表达式

在前文对语义检查进行简介时,我们已初步介绍过用于对二元运算符表达式进行语义检查的函数CheckBinaryExpression,为了阅读方便,这里我们再次给出图4.2.2。在本小节中,我们准备对第1126至1144行中的各个函数进行讨论。

图4.2.2 CheckBinaryExpression()

对于形如a+b的二元运算表达式,我们要通过在前面章节中介绍的函数CommonRealType来求得整个表达式a+b的类型,如果a为int型且b为double型,则表达式a+b的类型为double。在图4.2.40中给出的宏定义PERFORM_ARITH_CONVERSION中,第14行调用了CommonRealType函数用来求公共类型,而第15和第16行则分别对二元运算符的左操作数和右操作数进行必要的转型操作。第6行的宏SWAP_KIDS用于交换左右操作数,例如当遇到形如i
+ ptr的表达式,其中i为整数,而ptr为指针,我们如果希望左操作数为指针类型,而右操作数为整数类型,则可使用宏SWAP_KIDS来交换左右操作数,进而得到ptr+i。第19行的REPORT_OP_ERROR用于报错,表示遇到了非法的运算符。

图4.2.40          宏PERFORM_ARITH_CONVERSION

接下来我们来分析一下图4.2.2第1126至1143行中列出的CheckEqualityOP等函数,其代码如图4.2.41所示。第2行我们给出了用于判断是否为算术类型的宏IsArithType,其定义在ucl\type.h,第3行的IsScalarType用于判断类型是否为标量类型。结合第3行的POINTER等枚举常量及其在type.h中的枚举定义,不难读懂type.h中的其他几个宏,如BothScalarType等,这里不再啰嗦。图4.2.41第5至24行的代码用于对形如a==b或a!=b的表达式进行语义检查,如果操作数a和b都是算术类型(即整型和浮点类型),则在第12用宏PERFORM_ARITH_CONVERSION来完成必要的类型转换。表达式a==b或a!=b的结果为真或假,在C语言中,我们用int类型来表示布尔值,所以第13行置表达式的类型为int。第14行调用FoldConstant函数进行必要的常量折叠,例如对于3
== 2这样的表达式,没有必要到运行时再求值,编译时我们就可以知道表达式3 == 2的结果为0。

图4.2.41 CheckEqualityOP()

图4.2.41第16至21行则是按照C标准ucc\ansi.c.txt第3.3.9节” Equality operators”中规定的语义规则进行编写的,如下所示:

(1) both operands are pointers to qualified or unqualified versions ofcompatible types;

两个指针是类型相容的指针,我们在前面的章节中介绍过“相容类型”的概念。对应上图第16行。

(2)one operand is apointer to an object or incomplete type and the other is a qualified orunqualified version of void ; 一个指针是指向数据对象的指针,另一个是void *。对应上图第17和18行。

(3)one operand is apointer and the other is a null pointer constant. 一个是指针类型,而另一个为NULL。对应上图第19和20行。

图4.2.41第25至31行用于处理a&b、a|b和a^b这样的位运算,这些运算符要求操作数为整型;而第32至39行则用于处理形如a && b和a||b这样的短路运算,第34行的if条件要求两个操作数都为标量类型(即整型、浮点型和指针类型等);第41至54行的代码用于对a/b、a*b和a%b进行语义检查,取余运算%要求两个操作数都为整型,而乘除运算则可以是整型或浮点型;第55至61行的函数则用于检查形如a<<b和a>>b这样的移位运算,这里需要注意的是,我们并没有使用宏PERFORM_ARITH_CONVERSION来求公共类型,其原因在于表达式a>>b是进行算术右移还是逻辑右移,要取决于“左操作数a为有符号整数还是无符号整数”,所以在第60行我们置表达式a>>b的类型为第一个操作数a的类型。图4.2.41中的代码不算复杂,我们就不再啰嗦。

接下来,我们来分析一下CheckAddOP等函数,如图4.2.42所示。第1至26行的代码用于处理形如a+b的表达式,而第27至48行的代码则对形如a-b的表达式进行语义检查。对于a+b而言,第9至11行用于处理两个操作数都为算术类型的情况;当一个操作数为指针类型,而另一个操作数为整型,表达式ptr+i进行的是C语言的指针加法运算,在汇编代码层次真正执行的加法为ptr
+ k,其中k为i*sizeof(*ptr),第21行调用的ScalePointerOffset函数用于构造进行乘法运算的语法树结点。而对于a-b来说,第30至33行用于处理减法的两个操作数都是算术类型的情况,第34至39行用于处理形如ptr-i的指针运算,跟ptr+i的指针运算类似,我们需要在第37行调用ScalePointerOffset函数来构造i*sizeof(*ptr)的乘法运算。第41至48行则用于处理形如ptr1-ptr2的指针减法运算,例如,对于intarr[3]而言,&arr[2]-&arr[0]的含义是&arr[2]与&arr[0]之间相差几个int整数,而不是它们的地址相差几个字节,因此在汇编代码层次,我们真正执行的运算为(ptr2-ptr1)/sizeof(*arr),第44行调用的函数PointerDifference用于构造相应的除法运算结点。第49至60行给出了该函数的代码,第53的CREATE_AST_NODE用于创建语法树结点,第55行置其运算符为OP_DIV,即除法。

图4.2.42 CheckAddOP()

下面,我们来讨论赋值运算表达式的语义检查。按照C的语义,在表达式a+=b中,a只被计算一次,而在a = a+b中,a却要被计算两次。例如,对以下代码而言,在表达式*f()+= 3中,函数f()只被调用一次,而在表达式*f() += *f() + 3中,函数f()需要被调用两次。因此,当我们把a += b转换为a = a + b来处理,也要保持这样的语义不变。

int * f(void){

static int number;

printf("int *f(void) \n");

return &number;

}

int main(){

*f() += 3;

*f() += *f() + 3;

return 0;

}

对于在C源代码中就写为a = a + b的表达式来说(为表述方便,不妨记为a1 = a2 +b),在语法树上,有两个结点与a对应,即a1和a2各对应一个语法树结点;但对a+=b而言,只有一个语法树结点与a对应,语义检查时,我们把a+=b转换为a = a’ + b来处理后,并不会为a构造新的语法树结点。为了与在C源代码中就写为a=a+b的表达式有所区别,我们不妨把语义检查后由a+=b得来的表达式写为
a= a’+b,其中a和a’对应的是同一个语法树结点。

有了这个基础后,让我们来分析一下对赋值运算表达式进行语义检查的函数CheckAssignmentExpression。赋值运算符左侧的操作数必须是左值,且该操作数在声明时不应有限定符const,如果左侧操作数是结构体对象,则在该结构体的定义中,所有的成员域都不应有const限定符,图4.2.43第38至44行的CanModify()函数完成了这些判断。第13行调用CanModify()函数来检查一下左操作数是否为可写的左值。第18至27行用于把形如
a += b的表达式转换为a = a’+b来处理,我们只在第20行创建了一个新的语法树结点用来存放运算符+,而a和a’始终对应同一个语法树结点。对于a = a’+b来说,第26行实际上是调用CheckAddOP函数来对加法运算进行语义检查。第29行调用的CanAssign函数用于检测赋值运算符两侧的操作数在类型上是否匹配,我们在前面的章节中分析过CanAssign函数。

图4.2.43 CheckAssignmentExpression()

图4.2.44给出了表达式a+=b在语法分析后和语义检查后的语法树,由该图我们可以很清楚地看到,在把a+=b转换为a=a’+b后,语法树上a对应的结点仍旧只有一个。语义检查后,我们还会在语法树上添加各结点的类型信息,此处为简单起见,我们忽略了这些信息。

图4.2.44  a+=b的语法树

然后,我们来讨论一下对形如a?b:c的条件表达式的语义检查,与其相关的代码如图4.2.45所示。条件表达式实际上可被当作是三元运算符,即有a、b和c这3个操作数。图4.2.45第6至12行用于对第一个操作数a进行语义检查,第9行要求第一个操作数a为标量类型,第13至18行递归地调用CheckExpression函数,来对第2个操作数b和第3个操作数c分别进行语义检查。

图4.2.45 CheckConditionalExpression()

图4.2.45第19至50行的代码,用于对形如a?b:c中的操作数b和c的类型进行检查,这些代码是按照C标准ucc\ansi.c.txt的第“3.3.15 Conditional operator”节的语义规则编写的,如下所示:

(1)both operands have arithmetic type,即b和c的类型都为算术类型,对应上图第

22至28行,此时整个条件表达式的类型为b和c的公共类型。

(2)both operands have compatible structure or union types,即b和c的类型为相容

的结构体或联合体类型,对应上图第29至30行。

(3)both operands have void type,即b和c的类型都为void,对应上图第31至32

行,此时整个条件表达式的类型就为void。

(4)both operands are pointers to qualified or unqualified versions ofcompatible types,

即b和c为相容的指针类型,对应上图第33至36行,此时整个条件表达式的类型为b和c类型的合成类型。

(5)one operand is a pointer and the other is a null pointer constant,即一个为指针类

型,另一个为NULL,对应上图第37至42行。

(6)one operand is a pointer to an object or incomplete type and the otheris a pointer

to aqualified or unqualified version of void,即一个是指向数据对象的指针(即不是指向函数的指针),另一个是void *。

除了以上这6种情况外,b和c的其他类型都被视为非法的,图4.2.45第48行会进行报错。图4.2.45中调用的CommonRealType和CompositeType等函数,我们都已在前面的章节中做过讨论。至此,我们完成了对C语言表达式的语义检查,相关的代码主要在exprchk.c中,可以发现后缀表达式和一元表达式的语义是相对比较复杂的,例如,后缀运算符[]和一元运算符*都涉及到了内存寻址。对这些运算符的语义检查,有助于我们更深入地理解C语言。

时间: 2024-08-09 05:40:03

C编译器剖析_4.2 语义检查_表达式的语义检查(7)_二元运算符_赋值运算_条件表达式的相关文章

C编译器剖析_4.2 语义检查_表达式的语义检查(6)_一元运算符表达式

在这一小节中,我们来讨论一元运算符表达式的语义检查,与其相关的代码如图4.2.35所示.对于"前加加"和"前减减"运算符而言,我们采取的策略跟处理"后加加"和"后减减"一样,都是将--a转换为a -= 1,而将++a转换为a += 1,所以图4.2.35第5行调用的函数,就是我们在讨论后缀表达式语义检查时介绍过的函数TransformIncrement().对于形如+a或-a的表达式,我们需要检查一下a是否为算术类型,由于表达

C编译器剖析_4.2 语义检查_表达式的语义检查(5)_结构体成员选择

4.2.5         成员选择运算符 在C语言中,结构体struct和联合体union被称为记录类型RecordType,在形如dt.a和ptr->a的后缀表达式中,运算符.和->被称为成员选择运算符.函数CheckMemberAccess()用于对这些表达式进行语义检查,与之相关的代码如图4.2.28所示.在表达式dt.a中,dt和a相当于是运算符.的两个操作数,dt对应的语法树结点的类型应是记录类型,图4.2.28第8行对此进行了判断:而在形如ptr->a的表达式中,ptr对应

C编译器剖析_4.2 语义检查_表达式的语义检查(3)_字符串与标识符

4.2.3 在这一小节,我们先来分析一下基本表达式PrimaryExpression的语义检查,由C的标准文法,我们可以知道与PrimaryExpression相关的产生式如下所示,即加了一对小括号的表达式(Expression)在语法上也相当于标志符ID.常量CONST和字符串StringLiteral. primary-expression: ID constant string-literal ( expression ) 例如,对于表达式(a+b)+c而言,(a+b)和c都是基本表达式P

C编译器剖析_4.2 语义检查_表达式的语义检查(4)_函数调用

4.2.4 函数调用的语义检查 在这一小节中,我们来讨论一下函数调用的语义检查,语法上,函数调用对应的表达式属于后缀表达式PostfixExpression,UCC编译器exprchk.c的函数CheckFunctionCall()完成了对函数调用的语义检查,如图4.2.18所示.在阅读这份代码时,需要对语法分析后为函数调用构造的语法树有较好认识,请先参照"图3.1.21后缀运算符对应的语法树"或者先预览一下图4.2.19. 图4.2.18 CheckFunctionCall() 对形

C编译器剖析_4.4 语义检查_外部声明_类型结构的构建(2)

在这一小节中,我们将对形如第3章图3.3.17所示的结构体语法树进行语义检查,从而构建结构体的类型结构. 图3.3.17 ParseStructOrUnionSpecifier()构建的语法树 我们在第2章中给出了以下结构体struct Data对应的类型结构,如图2.4.4所示.为了阅读方便,我们重新给出这2幅图,由图示我们也能较清楚地预览本节的起点和终点. struct Data{ int abc:8; int def:24; double f; } dt; 图2.4.4   结构体的类型结

【java解惑】条件表达式结果类型规则

如下代码: public class Example008 { public static void main(String[] args) { char x = 'X'; int i = 0; System.out.println(true ? x : 65535); //1 System.out.println(true ? x : 65536); //2 System.out.println(true ? x : i); //3 System.out.println(false ? 0 :

Ember.js 入门指南——handlebars条件表达式

本系列文章全部从(http://ibeginner.sinaapp.com/)迁移过来,欢迎访问原网站. handlebars模板提供了与一般语言类似的条件表达式,比如if.if--else--. 在介绍这些条件表达式之前,我们先做好演示的准备工作.首先我会使用Ember CLI名称创建route.template,然后在生成的template上编写handlebars模板代码. 先生成route: ember generate route handlbars-conditions-exp-ro

(一)Python入门-4控制语句:02单分支选择结构-条件表达式详解

一:选择结构介绍 选择结构通过判断条件是否成立,来决定执行哪个分支.选择结构有多种形式,分为:单分 支.双分支.多分支.流程图如下: 二:单分支选择结构 if语句单分支结构的语法形式如下: if 条件表达式: 语句/语句块 其中:1条件表达式:可以是逻辑表达式.关系表达式.算术表达式等. 2语句/语句块:可以是一条语句,也可以是多条语句.多条语句,缩进必须对齐一致. 三:条件表达式详解 在选择和循环结构中,条件表达式的值为 False的情况如下: False.0.0.0.空值 None.空序列对

Simplifying Conditional Expressions(简化条件表达式)

1.Decompose Conditional(分解条件表达式) 2.Consolidate Conditional Expressions(合并条件表达式) 3.Consolidate Duplicate Conditional Fragments(合并重复的条件片段) 4.Remove Control Flag(移除控制标记) 5.Replace Nested Conditional with Guard Clauses(以卫语句取代嵌套条件表达式) 6.Replace Conditiona