Javac如何编译程序。

转化步骤

    首先,要读取源代码,一个字节为一节的度进来,找出在这些字节中有哪些是我们定义的语法关键词,如Java中的if、else、for、while等关键词:要识别哪些if是合法的关联词,哪些不是,这个步骤就是词法分析过程。
    此法分析的结果就是从源代码中找出一些规范化的Token流,就像在人类语言中,给你一句话,你要能分辨出其中哪些是词语,哪些是标点符号,哪些是动词,哪些是名词等。
    接着,就是对这些Token流进行语法分析了,这一步就是检查这些关键词组合在一起是不是符合Java语言规范,如在if的后面是不是紧跟着一个布尔判断表达式。就像人类语言中是不是有主谓宾,主谓宾结合得是否正确,语法是否正确。
    语法分析的结果就是形成一个符合Java语言规范的抽象语法树。抽象语法树是一个结构化的语法表达形式,他的作用是把语言的主要词法用一个结构化的形式组织在一起,就像我们大学中所学的离散数学,用数字的形式来表达非数字但又有复杂关系的物质世界。对这棵语法书我们在后面按照新的规则再重新组织,这也是编译器的关键所在。
    接下来是语义分析,虽然上面一部中语法分析已经完成,也就是不存语法问题了,但是语义是不是正确呢?语义分析的主要工作是把一些难懂的、复杂的语法转换成更加简单的语法,将这个步骤对应到我们人类的语言中,就是将难懂的文言文转化成大家都能懂的白话文,或者注解一下一些成语,便于人们更好的理解。
    语义分析的结果就是将复杂的语法转化成最简单的语法,对应到Java中,如将foreach转成for循环结构,还有注解等,最后形成一个注解过后的抽象语法树,这棵语法树更接近目标语言的语法规则。
    最后,通过字节码生成器生成字节码,根据经过注解的抽象语法树生成字节码,也就是将一个数据结构转化为另外一个数据结构,就像将所有的中文次域翻译成英语单词后,按照英语语法组装成英语语句。
    代码生成器的结果就是生成符合Java虚拟机规范的字节码了。
    Javac的各个模块完成了将Java源代码转化成Java字节码的任务,所以Javac主要就有4个模块,分别是词法分析器、语法分析器、语义分析器和代码生成器。

词法分析器

JavaC的主要词法分析其的接口类是com.sun.tools.javac.parser.Lexer,他的默认实现类是com.sun.tools.javac.parser.Scanner,Scanner会逐个读取Java文件的单个字符,然后解析出符合Java语言规范的Token序列。
词法分析器的作用是将Java源文件的字符流转变成对应的Token流。

语法分析器

语法分析器的作用是将词法分析器分析的Token流组建成更加结构化的语法树,也就是将一个个单词组装成一句话,一个完整的语句。哪些词语组合在一起是主语、哪些是谓语、哪些是宾语、哪些是定语等,要做进一步区分。
JavaC的语法树使得Java源码更加结构化,这种结构化可以为后面的进一步处理提供方便。每个语法树上的节点都是com.sun.tools.javac.tree.JCTree的一个实例,关于语法树规则如下。
    每个语法节点都会实现一个接口xxxTree,这个接口又继承自com.sun.source.tree.Tree接口,如IFTree语法节点表示一个if类型的表达式,BinaryTree语法节点代表一个二元操作表达式等。
    每个语法节点都是com.sun.tools.javac.tree.JCTree的子类,并且会实现第一节点中的xxxTree接口类,这个类的名称类似于JCxxx,如实现IfTree接口的实现类为JCIf,实现BinaryTree接口的类为JCBinary等。
    所有的JCxxx类都作为一个静态内部类定义在JCTree类中。
关于语法分析器还有一点要说明的是,所有语法节点的生成都是在TreeMaker类中完成的,TreeMaker实现了在JCTree.Factory接口中定义的所有节点的构成方法,通过该类你也能够发现在Java中到底有多少种语法节点。

语义分析器

将在Java类中的符号输入到符号表中主要由com.sun.tools.javac.comp.Enter类来完成,这个类主要万册灰姑娘以下两个步骤。

  1.     将在所有类中出现的符号输入到类自身的符号表中,所有类符号、类的参数类型符号(泛型参数类型)、超类符号和继承的接口类型符号等都存储到一个未处理的列表中。
  2.     将这个未处理列表中所有的类都解析到各自的类符号列表中,这个操作是在MemberEnter.complete()中完成的。

下一个步骤是处理annotation(注解),这个步骤是由com.sun.tools.javac.processing.JavacProcessingEnvironment类完成的。
再接下来是com.sun.tools.javac.comp.Attr(标注),这个步骤最重要的是检查语义合法性并进行逻辑判断,如以下几点。

  •     变量的类型是否匹配。
  •     变量在使用前是否已经初始化。
  •     能够推到出泛型方法的参数类型。
  •     字符串常量的合并。

在这个步骤中除Attr之外还需要另外一些类来协助,如下所示。

  •     com.sun.tools.javac.comp.Check,辅助Attr类检查语法树中的变量类型是否正确,如二元操作符两边的操作数的类型是否匹配,方法返回的类型是否与接收的引用值类型匹配等。
  •     com.sun.tools.javac.comp.Resolve,主要检查变量、方法或者类的访问是否合法、变量是否是静态变量、变量是否已经初始化等。
  •     com.sun.tools.javac.comp.ConstFold,常量折叠,这里主要针对字符串常量,会将一个字符串常量中的多个字符串合并成一个字符串。
  •     com.sun.tools.javac.comp.Infer,帮助推导泛型方法的参数类型等。

标注完成后由com.sun.tools.javac.comp.Flow类完成数据流分析,数据流分析主要完成如下工作。

  •     检查变量在使用前是否都已经被正确赋值。除了Java中的原始类型都会有默认的初始化值,其他像String类型和对象的引用都必须在使用前先赋值。
  •     保证final修饰的变量不会被重复赋值。经过final修饰的变量只能赋一次值,重复赋值会在这一步编译时报错,如果这个变量是静态变量,则在定义时就必须对其赋值。
  •     要确定方法的返回值类型。这里需要检查方法的返回值类型是否确定,并检查接受这个方法返回值的引用类型是否匹配,如果没有返回值,则不能有任何引用类型指向方法的这个返回值。
  •     所有的Checked Exception都要捕获或者向上抛出。例如我们使用FileInputStream读取一个文件时,必须捕获可能抛出的FileNotFondException异常,或者直接向上层方法抛出这个异常。
  •     所有的语句都要被执行到。这里会检查是否有语句出现在一个return方法的后面,因为在return方法后面的语句永远也不会被执行到。

语义分析的最后一个步骤是执行com.sun.tools.javac.comp.Flow,这是在进一步对语法树进行语义分析。总结如下。

  •     去掉无用的代码,只有永假的if代码块。
  •     变量的自动转换,如将int自动包装成Integer类型或者相反的操作等。
  •     取出语法糖,将foreach的形式转化成更简单的for循环形式。

代码生成器

Java会调用com.sun.tools.javac.jvm.Gen类遍历语法树,生成最终的Java字节码。
生成Java字节码需要经过以下两个步骤。

  1.     将Java方法中的代码块转化成符合JVM语法的命令格式,JVM的操作都是基于栈的,所有的操作都必须经过出栈和进栈来完成。
  2.     按照JVM的文件组织格式将字节码输出到以class为扩展名的文件中。

生成字节码除Gen类之外还有两个非常重要的辅助类,如下所述。

  •     Items,这个类表示任何可寻址的操作项,包括本地变量、类实例变量或者常量池中用户自定义的常量等,这些操作项都可以作为一个单位出现在操作栈上。
  •     Code,存储生成的字节码,并提供一些能够映射操作码的方法。

猜你喜欢

转载自blog.csdn.net/en_joker/article/details/81383243
今日推荐