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对应的结点类型应为“指向记录”的指针,第15行的if语句对此进行了检查。第4行用于对成员选择运算符的左操作数进行语义检查,结构体对象dt可当作左值,由此dt.a也可充当左值,但由于函数的返回值只存放在临时变量中,不可充当左值,所以GetData().a也不可充当左值,第11行对此进行了设置。对ptr->a来说,左操作数不必是左值,即使是个临时变量也可,例如在表达式((Data
*)ptr2)->a中,强制转型后的结果((Data*)ptr2)就存放在一个临时变量中,但ptr或((Data *)ptr2)的值代表的是一个可寻址内存单元的地址,所以我们可把ptr->a当作左值来用,第19行完成了这个设置。当然,即便表达式dt.a或者ptr->a对应的语法树结点的lvalue域为1,在对dt.a或者ptr->a进行赋值时,我们还会检查一下其类型信息,看看是否有const限定符,如第27行的注释所示,如果定义dt变量时有const的限定,那么dt.a也相当于有const限定符,第28行的Qualify()函数用于添加这个类型限定符。我们还需要在检查一下结构体Data中是否有域成员a,第21行的LookupField(Type
ty,char *id)函数用于此目的。给合我们在第2章给出的“图2.4.4         结构体的类型结构”,理解这个函数不会太难。如果在结构体的定义中找不到成员a,则说明dt.a是非法的表达式,则在第23行报错。我们把查询得到的关于域成员a的类型信息通过第29行的代码,存放到dt.a表达式对应的语法树结点上。与第3行的Field对应的结构体struct filed描述了域成员的相关信息,我们在第2章的“图2.4.3结构体的类型描述”中介绍过这个结构体,这里不再重复。

图4.2.28 CheckMemberAccess()

图4.2.29给出了表达式dt.a在语法分析后和语义检查后所对应的语法树,对op域为OP_ID的语法树结点的语义检查是由exprchk.c中的CheckPrimaryExpression函数来完成的,在语义检查后,域成员a在结构体struct Data中的偏移量offset等信息由图中的struct filed对象保存,而结构体对象dt对应的符号则由struct
symbol对象来存放。

图4.2.29 成员选择运算的语法树

接下来我们来看一下对“后加加”和“后减减”表达式的语义检查,例如a++和a--。与其相关的代码如图4.2.30所示。由图4.2.30第396和397行的注释,我们可以知道,UCC编译器会把a++和++a转换成a+=1来处理,而把a--和--a转换成a-=1来处理,第394行创建了一个语法树结点,第398行置其op域为OP_ADD_ASSIGN或OP_SUB_ASSIGN,即+=或者-=,在第402行我们调用CheckExpression()来对赋值运算符+=或者-=进行语义检查。需要注意的是,结点expr的op域仍然保存着原有的“后加加OP_POSTINC”或“后减减OP_POSTDEC”等运算符,这样在生成代码时,我们能为a++、a--、++a或者--
a进行区别,进而产生不同的代码。

图4.2.30 TransformIncrement()

至此,历经一番曲折,我们已经完成了对所有后缀表达式的语义检查。在后续章节中,我们会对一元运算表达式和二元运算表达式进行语义检查。沉舟侧畔千帆过,让我们继续扬帆前行。

时间: 2024-10-26 04:17:30

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

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   结构体的类型结

C编译器剖析_1.5 结合C语言来学汇编_指针、数组和结构体

让我们再来看一份C代码,及其经UCC编译器编译后产生的主要汇编代码,如图1.33所示,其中包含了数组.指针和结构体. 图1.33 数组.指针和结构体 按照C的语义,图1.33第9行的C代码是对局部数组number的初始化,需要把number[0]初始化为2015,而数组中的其他元素皆被初始化为0.UCC编译器采取的翻译方法是:先调用memset函数来把数组number所占的内存空间清0,然后再把number[0]设为2015,如图1.33的第17至24行所示.C库函数memset的API如下所示

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

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

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.2 语义检查_表达式的语义检查(7)_二元运算符_赋值运算_条件表达式

在前文对语义检查进行简介时,我们已初步介绍过用于对二元运算符表达式进行语义检查的函数CheckBinaryExpression,为了阅读方便,这里我们再次给出图4.2.2.在本小节中,我们准备对第1126至1144行中的各个函数进行讨论. 图4.2.2 CheckBinaryExpression() 对于形如a+b的二元运算表达式,我们要通过在前面章节中介绍的函数CommonRealType来求得整个表达式a+b的类型,如果a为int型且b为double型,则表达式a+b的类型为double.在

[C/C++标准库]_[初级]_[计算结构体成员的偏移量]

场景: 1. C结构体里计算结构体的偏移量平常看来没什么必要,但是放到插件结构的设计里就有必要了,比如只能使用偏移量访问的场景,而不能使用引用成员变量的场景. 2. 在设计一致性的接口时,公用的接口不怎么变化的,但是插件模块的结构可以不需要根据统一结构来设计,他们只需要提供偏移量给公用接口调用就行了,不同的插件 可能偏移量不一致,因为他们可以独立实现.公用接口就可以通过偏移量来访问不同的变量. 3. 可以使用stddef.h文件里的  offsetof /* Define offsetof ma

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行中再次遇到表达式a+b时,a和b的值并没有发生变化,我们可在第9行直接把t1赋给变量d.由于我

C编译器剖析_5.2.4 中间代码生成及优化_后缀表达式的翻译

5.2.4 后缀表达式的翻译 在前面的章节中,我们介绍了用于对数组元素和结构体成员进行访问的函数Offset,其接口如下所示,参数addr代表了基地址,参数voff代表可变偏移,而参数coff则代表常量偏移. Symbol Offset(Type ty, Symbol addr,Symbol voff, int coff): 函数Offset的基本想法是产生以下中间代码,我们要先对addr.voff和coff进行相加,得到目标地址(addr+voff+coff),然后再进行"提领Derefere