编译器开发系列--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”的值都是相同的。

时间: 2024-10-13 12:19:08

编译器开发系列--Ocelot语言5.表达式的有效性检查的相关文章

编译器开发系列--Ocelot语言1.抽象语法树

从今天开始研究开发自己的编程语言Ocelot,从<自制编译器>出发,然后再自己不断完善功能并优化. 编译器前端简单,就不深入研究了,直接用现成的一款工具叫JavaCC,它可以生成抽象语法树,抽象语法树是生成中间代码的关键,而中间代码又是生成后端代码的关键. 整个编译器代码采用java语言编写,主要功能是对JavaCC生成的抽象语法树进行语义分析.优化,最后生成优化后的汇编代码,然后再用汇编器对汇编代码汇编生成机器码,最后再用命令链接生成Linux可执行文件,就可以直接在Linux上运行了. 整

编译器开发系列--Ocelot语言2.变量引用的消解

"变量引用的消解"是指确定具体指向哪个变量.例如变量"i"可能是全局变量i,也可能是静态变量i,还可能是局部变量i.通过这个过程来消除这样的不确定性,确定所引用的到底是哪个变量. 为了消除这样的不确定性,我们需要将所有的变量和它们的定义关联起来,这样的处理称为"变量引用的消解".具体来说,就是为抽象语法树中所有表示引用变量的VariableNode 对象添加该变量的定义(Variable 对象)的信息. LocalResolver就是用来处理变量

编译器开发系列--Ocelot语言3.类型名称的消解

"类型名称的消解"即类型的消解.类型名称由TypeRef 对象表示,类型由Type 对象表示.类型名称的消解就是将TypeRef 对象转换为Type 对象. TypeResolver 类的处理仅仅是遍历抽象语法树,发现TypeRef 的话就从叶子节点开始将其转换为Type 类型.类型和变量的不同之处在于没有作用域的嵌套(作用域唯一),因此没 有必要使用栈. [TypeRef 对象和Type 对象的对应关系保存在TypeTable 对象中.] 其中Type为类型的定义.struct po

IOS开发系列--C语言之指针

概览 指针是C语言的精髓,但是很多初学者往往对于指针的概念并不深刻,以至于学完之后随着时间的推移越来越模糊,感觉指针难以掌握,本文通过简单的例子试图将指针解释清楚,今天的重点有几个方面: 什么是指针 数组和指针 函数指针 什么是指针 存放变量地址的变量我们称之为"指针变量",简单的说变量p中存储的是变量a的地址,那么p就可以称为是指针变量,或者说p指向a.当我们访问a变量的时候其实是程序先根据a取得a对应的地址,再到这个地址对应的存储空间中拿到a的值,这种方式我们称之为"直接

IOS开发系列--C语言之构造类型

概述 在第一节中我们就提到C语言的构造类型,分为:数组.结构体.枚举.共用体,当然前面数组的内容已经说了很多了,这一节将会重点说一下其他三种类型. 结构体 枚举 共用体 结构体 数组中存储的是一系列相同的数据类型,那么如果想让一个变量存储不同的数据类型就要使用结构体,结构体定义类似于C++.C#.Java等高级语言中类的定义,但事实上它们又有着很大的区别.结构体是一种类型,并非一个变量,只是这种类型可以由其他C语言基本类型共同组成. // // main.c // ConstructedType

IOS开发系列--C语言之生存储方式和作用域

概述 基本上每种语言都要讨论这个话题,C语言也不例外,因为只有你完全了解每个变量或函数存储方式.作用范围和销毁时间才可能正确的使用这门语言.今天将着重介绍C语言中变量作用范围.存储方式.生命周期.作用域和可访问性. 变量作用范围 存储方式 可访问性 变量作用范围 在C语言中变量从作用范围包括全局变量和局部变量.全局变量在定义之后所有的函数中均可以使用,只要前面的代码修改了,那么后面的代码中再使用就是修改后的值:局部变量的作用范围一般在一个函数内部(通常在一对大括号{}内),外面的程序无法访问它,

IOS开发系列--C语言之预处理

概述 大家都知道一个C程序的运行包括编译和链接两个阶段,其实在编译之前预处理器首先要进行预处理操作,将处理完产生的一个新的源文件进行编译.由于预处理指令是在编译之前就进行了,因此很多时候它要比在程序运行时进行操作效率高.在C语言中包括三类预处理指令,今天将一一介绍: 宏定义 条件编译 文件包含 宏定义 对于程序中经常用到的一些常量或者简短的函数我们通常使用宏定义来处理,这样做的好处是对于程序中所有的配置我们可以统一在宏定义中进行管理,而且由于宏定义是在程序编译之前进行替换相比定义成全局变量或函数

iOS开发系列--C语言之基础知识

概览 当前移动开发的趋势已经势不可挡,这个系列希望浅谈一下个人对IOS开发的一些见解,这个IOS系列计划从几个角度去说IOS开发: C语言 OC基础 IOS开发(iphone/ipad) Swift 这么看下去还有大量的内容需要持续补充,但是今天我们从最基础的C语言开始,C语言部分我将分成几个章节去说,今天我们简单看一下C的一些基础知识,更高级的内容我将放到后面的文章中. 今天基础知识分为以下几点内容(注意:循环.条件语句在此不再赘述): Hello World 运行过程 数据类型 运算符 常用

iOS开发系列--Swift语言

Swift是苹果2014年推出的全新的编程语言,它继承了C语言.ObjC的特性,且克服了C语言的兼容性问题.Swift发展过程中不仅保留了 ObjC很多语法特性,它也借鉴了多种现代化语言的特点,在其中你可以看到C#.Java.Javascript.Python等多种语言的影子.同时在 2015年的WWDC上苹果还宣布Swift的新版本Swift2.0,并宣布稍后Swift即将开源,除了支持iOS.OS X之外还将支持linux. 本文将继续iOS开发系列教程,假设读者已经有了其他语言基础(强烈建