编译器开发系列--Ocelot语言5.表达式的有效性检查
本篇将对“1=3”“&5”这样无法求值的不正确的表达式进行检查。
将检查如下这些问题。
●为无法赋值的表达式赋值(例:1 = 2 + 2)
●使用非法的函数名调用函数(例:"string"("%d\n", i))
●操作数非法的数组引用(例:1[0])
●操作数非法的成员引用(例:1.memb)
●操作数非法的指针间接引用(例:1->memb)
●对非指针的对象取值(例:*1)
●对非左值的表达式取地址
具体例子以及问题的检测方法如表10.1所示,其中包括了刚才列举的问题。
非指针类型取值操作的检查
/*非指针类型取值操作的检查 * 表示取值运算符(*)的DereferenceNode的处理。 * 该方法检查取值运算符的操作数的类型是否为指针。 */ // #@@range/DereferenceNode{ public Void visit(DereferenceNode node) { /* * 首先,通过super.visit(node) 调用基类Visitor 的方法遍历操作数(node.expr()) (即检查操作数)。 */ super.visit(node); /* * 接着,调用操作数node.expr() 的isPointer 方法,检查操作数的类型是否是指针, 即检查是否可以进行取值。如果无法取值,则调用undereferableError 方法输出编译错误。 */ if (! node.expr().isPointer()) { undereferableError(node.location()); } /* * 最后,调用handleImplicitAddress 方法对数组类型和函数类型进行特别处理。该处 理还和接下来AddressNode 的处理相关, */ handleImplicitAddress(node); return null; }
获取非左值表达式地址的检查
/*获取非左值表达式地址的检查 * 检查操作数是否为左值。表示地址运算符的AddressNode 的处理 */ // #@@range/AddressNode{ public Void visit(AddressNode node) { super.visit(node); /* * 首先对node.expr() 调用isLvalue 方法,检查&expr 中的expr 是否是可以进行取 址操作的表达式。 ExprNode#isLvalue 是检查该节点的表达式是否能够获取地址的方法。 */ if (! node.expr().isLvalue()) { semanticError(node.location(), "invalid expression for &"); } /* * 剩余的语句用于确定AddressNode 的类型。通常node.expr().isLoadable() 会 返回true,即执行else 部分的处理。&expr 的类型是指向expr 类型的指针,因此指向 node.expr().type() 的指针类型可以作为节点整体的类型来使用。 */ Type base = node.expr().type(); /* * 在将puts 的类型设置为指向函数的指针的同时,还必须将&puts 的类型也设置为指向函 数的指针。 node.expr() 的类型是数组或函数的情况下进行特别处理,使得&puts 的类型 和puts 的类型相一致。 */ if (! node.expr().isLoadable()) { // node.expr.type is already pointer. node.setType(base); } else { node.setType(typeTable.pointerTo(base)); } return null; }
隐式的指针生成
单个数组类型或函数类型的变量表示数组或函数的地址。例如,假设变量puts 的类型为函数类型(一般称为函数指针),那么puts 和&puts 得到的值是相同的。
/* * handleImplicitAddress 方法将数组类型或函数类型转换为了指向 数组或函数类型的指针,即隐式地生成指针类型。 */ private void handleImplicitAddress(LHSNode node) { if (! node.isLoadable()) { Type t = node.type(); if (t.isArray()) { // int[4] ary; ary; should generate int* node.setType(typeTable.pointerTo(t.baseType())); } else { node.setType(typeTable.pointerTo(t)); } } }
puts 是指向函数的指针,因此它的取值运算*puts 的结果是函数类型,但这样又会隐式地转换为指向函数的指针。*puts 还是指向函数的指针,因此仍然可以进行取值运算,仍然会转换为指向函数的指针。像这样可以无限重复下去。所以C 语言中“&puts”“puts”“*puts”“**puts”“***puts”的值都是相同的。