精华内容
下载资源
问答
  • java开发C语言编译器

    2017-07-30 18:56:00
    http://study.163.com/course/introduction.htm?courseId=1003169025 http://study.163.com/course/courseMain.htm?courseId=1002830012 http://blog.csdn.net/tyler_download/article/category/6097178

    http://study.163.com/course/introduction.htm?courseId=1003169025

    http://study.163.com/course/courseMain.htm?courseId=1002830012

    http://blog.csdn.net/tyler_download/article/category/6097178

    展开全文
  • 无需自己实现,直接可以调用的函数,我们都称为库函数,或是API, 本节,我们要为当前构建的虚拟机提供C语言库函数,我们要给解释器提供一种函数调用机制,这些函数无需程序自己实现,而是由我们的解释器提供的,...

    更详细的讲解和代码调试演示过程,请参看视频
    用java开发C语言编译器

    我们第一次使用C语言开发程序时,往往是在控制台上打印一句”Hello World”,实现打印语句功能的函数是printf, 这个函数是有C语言的链接库提供的,开发者可以直接调用,类似于这种无需自己实现,直接可以调用的函数,我们都称为库函数,或是API, 本节,我们要为当前构建的虚拟机提供C语言库函数,我们要给解释器提供一种函数调用机制,这些函数无需程序自己实现,而是由我们的解释器提供的,C语言程序直接调用即可,这节,我们要实现的一个库函数就是printf. 完成本节代码后,我们的解释器能解释执行下面程序:

    void main() {
    printf("a is %d:",1);
    }

    printf函数是我们解释器提供给代码的,有了库函数,程序的开发便能高效很多。

    库函数机制的实现由新构造的类ClibCall,我们先看看它的实现代码:

    package backend;
    
    import java.util.ArrayList;
    import java.util.HashSet;
    import java.util.Set;
    
    public class ClibCall {
        private Set<String> apiSet;
    
        private ClibCall() {
            apiSet = new HashSet<String>();
            apiSet.add("printf");
        }
        private static ClibCall instance = null;
    
        public static ClibCall getInstance() {
    
            if (instance == null) {
                instance = new ClibCall();
            }
    
            return instance;
        }
    
        public boolean isAPICall(String funcName) {
            return apiSet.contains(funcName);
        }
    
        public Object invokeAPI(String funcName) {
            switch (funcName) {
    
            case "printf":
                return handlePrintfCall();
    
            default:
                return null;
            }
        }
    
        private Object handlePrintfCall() {
            ArrayList<Object> argsList = FunctionArgumentList.getFunctionArgumentList().getFuncArgList(false);
            String argStr = (String)argsList.get(0);
            String formatStr = "";
    
            int i = 0;
            int argCount = 1;
            while (i < argStr.length()) {
                if (argStr.charAt(i) == '%' && i+1 < argStr.length() && 
                        argStr.charAt(i+1) == 'd') {
                    i += 2;
                    formatStr += argsList.get(argCount);
                    argCount++;
                } else {
                    formatStr += argStr.charAt(i);
                    i++;
                }
            }
    
            System.out.println(formatStr);
    
            return null;
        }
    }
    

    ClibCall 包含了一个集合类叫apiSet, 其用于存储库函数的函数名,当代码中有函数调用时,解释器拿到被调函数的名字,提交给ClibCall, 该类会在apiSet中查找是否含有与给定名字相同的字符串,如果有,那意味着该函数属于库函数。

    由于目前我们只实现了一个库函数printf, 因此初始化时,我们把字符串”printf”加入到该集合中。 isAPICall 传入的是函数名,如果函数名包含在apiSet里面,那么返回true, 表明他是库函数调用。

    invokeAPICall 用来执行给定的库函数,传入参数是库函数的名称。在里面,解释器根据不同的库函数名称,去实现不同的库函数。handlePrintfCall用于实现printf调用,首先,它获得输入参数,第一个输入参数是要显示到控制台上的字符串,在字符串中,可能会含有格式化字符,当前我们实现的printf可接受的格式化字符是%d. 在printf的模拟实现中,我们遍历每一个字符,当遇到%d时,我们从参数列表中获得对应的数值,然后把数值替换%d格式符,最后通过println把格式化后的字符串打印到控制台上。

    有了库函数调用后,每当解释器解析到函数调用是,需要确定当前调用的函数是代码自己实现的,还是库函数提供的,这个机制的实现在UnaryExecutor中,代码如下:

    public class UnaryNodeExecutor extends BaseExecutor{
    
        @Override
        public Object Execute(ICodeNode root) {
            executeChildren(root);
    
            int production = (Integer)root.getAttribute(ICodeKey.PRODUCTION); 
            String text ;
            Symbol symbol;
            Object value;
            ICodeNode child;
    
            switch (production) {
            ....
            case CGrammarInitializer.Unary_LP_ARGS_RP_TO_Unary:
                //先获得函数名
                String funcName = (String)root.getChildren().get(0).getAttribute(ICodeKey.TEXT);
                if (production == CGrammarInitializer.Unary_LP_ARGS_RP_TO_Unary) {
                    ICodeNode argsNode = root.getChildren().get(1);
                    ArrayList<Object> argList = (ArrayList<Object>)argsNode.getAttribute(ICodeKey.VALUE);
                    FunctionArgumentList.getFunctionArgumentList().setFuncArgList(argList); 
                }
    
                //找到函数执行树头节点
                ICodeNode func = CodeTreeBuilder.getCodeTreeBuilder().getFunctionNodeByName(funcName);
                if (func != null) {
                    Executor executor = ExecutorFactory.getExecutorFactory().getExecutor(func);
                    executor.Execute(func);
                    Object returnVal = func.getAttribute(ICodeKey.VALUE);
                    if (returnVal != null) {
                        System.out.println("function call with name " + funcName + " has return value that is " + returnVal.toString());
                        root.setAttribute(ICodeKey.VALUE, returnVal);
                    }
                } else {
                    ClibCall libCall = ClibCall.getInstance();
                    if (libCall.isAPICall(funcName)) {
                        Object obj = libCall.invokeAPI(funcName);
                        root.setAttribute(ICodeKey.VALUE, obj);
                    } 
                }
            ....
            }

    当解释器解析函数调用时,它现在函数调用表中,查找给定函数的语法执行树头结点,如果找不到的话,那么解释器知道,这个函数是库函数,于是调用ClibCall来处理,它先通过isAPICall来判断,给定函数是否是库函数,如果是的话,则调用invokeAPI来执行库函数提供的功能。

    有了ClibCall, 以后我们想要添加新的库函数时,只要修改ClibCall的实现即可,基于现在的架构基础上,我们今后可以快速的实现更多库函数,从而让我们的解释器越来越强大!

    更加具体的代码解释和调试过程请参看视频。

    更多技术信息,包括操作系统,编译器,面试算法,机器学习,人工智能,请关照我的公众号:
    这里写图片描述

    展开全文
  • 编译器C语言代码中对数组的相关操作编译成java字节码

    更详细的讲解和代码调试演示,请参看视频:
    更详细的讲解和代码调试演示过程,请参看视频
    用java开发C语言编译器

    jvm字节码对应于数组操作时有一套特定的指令,当字节码要在虚拟机上生成一个含有10个元素的整形数组时,它会执行以下指令:

    sipush 10
    newarray int
    astore 0

    第一条语句sipush 10 先将数组所包含的元素的个数压到堆栈上,生成数组时对应一条特定指令就是 newarray, 该指令后面跟着要生成的数组类型,如果想生成整形数组,那么newarray 指令后面就跟着int, 如果想生成浮点型数组,那么newarray 后面就得跟着float. 执行完这两条语句后堆栈顶部存储着含有10个元素的整形数组的对象实例:
    stack: int[10]

    接着的指令astore 0把堆栈上的含有10个元素的整形数组对象转移到局部变量队列:
    stack: null
    local_list: int[10]

    字节码要想读取数组中的某个元素的值时,例如要读取a[7]的值时,可以执行如下指令:

    aload 0
    sipush 7
    iaload

    aload 0指令把存储在局部变量队列的数组对象加载到堆栈,然后把要读取的数组元素的下标压入堆栈,由于我们要读取的数组元素的下标是7,因此我们通过指令sipush 7把数值7压入堆栈,最后指令iaload把a[7]的值压入到堆栈,由于我们刚生成的数组a[10]还没有初始化,所以a[7]的值是0,上面的指令执行后,虚拟机的情况如下:
    stack: 0, int[10]
    local_stack: int[10]

    指令iload前面的i与数组的类型要一致,如果数组类型是浮点型,对应的指令就应该是fload.

    如果要想对数组中的某个元素赋值,加入我们要把10赋值给数组下标为3的元素,也就是我们想实现a[3] = 10,那么我们可以执行如下指令:

    aload 0
    sipush 3
    sipush 10
    iastore 

    指令aload 0先把数组对象加载到堆栈,然后把要赋值的元素的下标压入堆栈,由于我们想对下标为3的元素赋值,所以通过指令sipush 3把数值3压到堆栈。接着把要赋值的内容压入堆栈,因为我们想给a[3]赋值10,所以通过sipush 10 把数值10 压入堆栈,最后执行指令iastore 把数值10存入数值下标为3的元素,iastore前面的i与数组的类型是相关的,如果数组类型是浮点值,那么对应的指令应该是fastore。

    有了上面的理论基础后,我们看看如何把下面的C语言代码编译成java字节码:

    void main() {
        int a[10];
        a[3] = 10;
        printf("value of a[3] is :%d", a[3]);
    }

    代码首先定义了一个含有10个元素的整形数组,当编译成java字节码时,我们的编译器首先需要使用相应指令在虚拟机中生成对应的数组对象,相应代码如下,在ProgramGenerator.java中:

    public void createArray(Symbol symbol) {
            if (arrayNameMap.containsKey(symbol.getScope())) {
                if (arrayNameMap.get(symbol.getScope()).equals(symbol.getName())) {
                    return;
                }
            }
    
            arrayNameMap.put(symbol.getScope(), symbol.getName());
    
            Declarator declarator = symbol.getDeclarator(Declarator.ARRAY); 
            if (declarator == null) {
                return;
            }
    
            String type = "";
            if (symbol.hasType(Specifier.INT)) {
                type = "int";
            }
    
            int num = declarator.getElementNum();
            this.emit(Instruction.SIPUSH, ""+num);
            this.emit(Instruction.NEWARRAY , type);
            int idx = getLocalVariableIndex(symbol);
            this.emit(Instruction.ASTORE, "" + idx);
        }

    symbol对应的就是变量a的Symbol对象,arrayNameMap的定义如下:

    private Map<String, String> arrayNameMap = new HashMap<String, String>();

    哈希表的key对应的是变量的作用域,哈希表的值对应变量名,由于上面代码中,变量a的作用域是”main”, 它的变量名是”a”,因此代码会在哈希表中加入一条记录(“main”,”a”)。代码执行是首先判断该变量对应的数组是否已经生成过,如果生成过,那就直接返回,如果没有生成过,那么先判断输入的变量是否属于数组类型,如果是,那就获取数组的类型,接着通过delcarator.getElementNum()获得数组的元素个数,通过getLocalVariableIndex()获得变量在队列中的位置,然后根据前面讲解过的数值生成指令,把相关指令输出到java汇编代码文件中。

    我们再看看读取数组元素的实现:

    public void readArrayElement(Symbol symbol, int index) {
            Declarator declarator = symbol.getDeclarator(Declarator.ARRAY); 
            if (declarator == null) {
                return;
            }
    
            int idx = getLocalVariableIndex(symbol); 
            this.emit(Instruction.ALOAD, ""+idx);
            this.emit(Instruction.SIPUSH, ""+index);
            this.emit(Instruction.IALOAD);
        }

    根据前面的理论,我们先通过aload指令把数组对象从队列加载到堆栈上,接着把要读取的元素下标压入队列,最后通过iaload指令把元素的值从数组中加载到堆栈上。

    接着是修改元素值的实现:

    public void writeArrayElement(Symbol symbol, int index, Object value) {
            Declarator declarator = symbol.getDeclarator(Declarator.ARRAY); 
            if (declarator == null) {
                return;
            }
    
            int idx = getLocalVariableIndex(symbol); 
            if (symbol.hasType(Specifier.INT)) {
                int val = (int)value;
                this.emit(Instruction.ALOAD, ""+idx);
                this.emit(Instruction.SIPUSH, ""+index);
                this.emit(Instruction.SIPUSH, ""+val);
                this.emit(Instruction.IASTORE);
            }
        }

    数组元素写入的实现跟我们前面的理论描述是一致的,先是通过指令aload把数组对象加载到堆栈,然后把要写入的元素下标压入堆栈,最后把要写入元素的值压入堆栈,接着执行iastore指令,把相关信息写入数组的相应元素。

    当编译器对代码进行解析时,遇到数组的读写,例如解析到语句a[3] = 10;时,在UnaryNodeExecutor.java中的以下代码会被调用:

    case CGrammarInitializer.Unary_LB_Expr_RB_TO_Unary:
                child = root.getChildren().get(0);
                symbol = (Symbol)child.getAttribute(ICodeKey.SYMBOL);
    
                child = root.getChildren().get(1);
                int index = (Integer)child.getAttribute(ICodeKey.VALUE);
    
                try {
                    Declarator declarator = symbol.getDeclarator(Declarator.ARRAY);
                    if (declarator != null) {
                        Object val = declarator.getElement(index);
                        root.setAttribute(ICodeKey.VALUE, val);
                        ArrayValueSetter setter = new ArrayValueSetter(symbol, index);
                        root.setAttribute(ICodeKey.SYMBOL, setter);
                        root.setAttribute(ICodeKey.TEXT, symbol.getName()); 
    
                        //create array object on jvm
    ProgramGenerator.getInstance().createArray(symbol);
                        ProgramGenerator.getInstance().readArrayElement(symbol, index);
                    }

    数组元素的读写对应的语法表达式是:

    UNARY -> UNARY LB EXPR RB

    执行该表达式的正是上面给定的代码,在读写数组元素时,我们先调用createArray在jvm的堆栈上生成数组对象,再调用readArrayElement来读取数组中给定元素的值。

    编译器中负责修改数组元素的部分是ArrayValueSetter类,所以我们也在里面进行相应指令的输出,代码如下:

     public void setValue(Object obj) {
            Declarator declarator = symbol.getDeclarator(Declarator.ARRAY);
            try {
                declarator.addElement(index, obj);
    
                ProgramGenerator.getInstance().writeArrayElement(symbol, index, obj);
                System.out.println("Set Value of " + obj.toString() + " to Array of name " + symbol.getName() + " with index of " + index);
            } catch (Exception e) {
                // TODO Auto-generated catch block
                System.err.println(e.getMessage());
                e.printStackTrace();
                System.exit(1);
            }
    
        }

    编译器在解读数组元素的修改语句时,顺便调用writeArrayElement生成jvm上对数组元素进行修改的指令。

    上面代码完成后运行,编译器把给定的C语言代码编译成如下的java汇编语言代码:

    .class public CSourceToJava
    .super java/lang/Object
    
    .method public static main([Ljava/lang/String;)V
        sipush  3
        sipush  10
        newarray    int
        astore  0
        aload   0
        sipush  3
        iaload
        sipush  10
        aload   0
        sipush  3
        sipush  10
        iastore
        sipush  3
        aload   0
        sipush  3
        iaload
        istore  1
        getstatic   java/lang/System/out Ljava/io/PrintStream;
        ldc "value of a[3] is :"
        invokevirtual   java/io/PrintStream/print(Ljava/lang/String;)V
        getstatic   java/lang/System/out Ljava/io/PrintStream;
        iload   1
        invokevirtual   java/io/PrintStream/print(I)V
        getstatic   java/lang/System/out Ljava/io/PrintStream;
        ldc "
    "
        invokevirtual   java/io/PrintStream/print(Ljava/lang/String;)V
        return
    .end method
    .end class
    

    把上面java汇编代码编译成二进制字节码,运行在虚拟机上的结果如下:
    这里写图片描述

    通过运行结果可见,我们编译器的实现是正确的。

    还需要提一下的是,我们的编译器在编译的时候,产生了冗余语句,因为编译器在解析源码时,一旦遇到数字字符串,它就会生成一条把对应数字进行压栈的语句,在上面java汇编代码中,第一条语句:

    sipush 3

    其实就是冗余语句,它是完全没有必要的,出现这条语句的原因是,当我们的编译器在解析语句a[3] = 10;的时候,读取到字符3的时候,它不管三七二十一,立马产生一条将常量压入堆栈的语句,也就是上面那条语句。解读数值常量的代码也是在UnaryNodeExecutor.java中,代码如下:

    case CGrammarInitializer.Number_TO_Unary:
            ....
                ProgramGenerator.getInstance().emit(Instruction.SIPUSH, "" + value);
                break;

    正是上面的代码导致编译器一旦解读到数值常量就立马输出一条sipush指令,虽然冗余语句不会对编译结果造成影响,但是它会让我们生成的最终代码在运行上的速度下降。

    正因为这个原因,在解读语句a[3]=10;时,读取到最后的数值10时,编译器又会生成一条冗余指令,也就是第12行的sipush 10.由于冗余语句的存在,会使得最终生成的java汇编代码与预想的多了一些指令,大家把结果编译出来后,读取最终Java汇编代码时,注意不要被迷惑,后面我们会想办法处理冗余指令这个问题。

    更详细的讲解和代码演示,请参看视频。

    更多技术信息,包括操作系统,编译器,面试算法,机器学习,人工智能,请关照我的公众号:
    这里写图片描述

    展开全文
  • 上一节,我们把C语言编译成了可以被java虚拟机加载执行的java汇编语言。这节,我们就jvm的基本机制进行深入了解,如果不理解java虚拟机的体系结构,那么我们不可能把C语言转换成能顺利在虚拟机上执行的字节码

    更详细的讲解和代码调试演示过程,请参看视频
    用java开发C语言编译器

    如果你对机器学习感兴趣,请参看一下链接:
    机器学习:神经网络导论

    上一节,我们把C语言编译成了可以被java虚拟机加载执行的java汇编语言。这节,我们就jvm的基本机制进行深入了解,如果不理解java虚拟机的体系结构,那么我们不可能把C语言转换成能顺利在虚拟机上执行的字节码。

    把握java虚拟机,必须把握一点,那就是java虚拟机的运行是以栈为基础的。理解了这点,对jvm的理解就掌握了一大半。java程序运行时,当某个函数被调用是,jvm会创建一个执行环境,这个环境叫stack frame, 这个环境有三个需要理解的要点,第一是,stack frame 含有一个执行堆栈,任何指令的执行,都会围绕这个堆栈来进行。第二是局部变量数组,函数的输入参数和局部变量就存储在这个数组中。第三个是指令指针PC,它指向下一条要执行的指令。

    举个例子,假设java 代码中有一个函数:

    int f(int a, int b) {
        return a+b;
    }

    上面代码编译后,jvm会产生的stack frame 如下:
    stack:
    local_array: a, b
    PC-> 把变量a 从local_array 取出,然后压入堆栈
    把变量b 从local_array 取出,然后压入堆栈
    把堆栈顶部两个元素弹出后相加,把结果压入堆栈
    把堆栈顶部的元素弹出后返回。

    假设函数的调用方式为 f(1,2) 那么stack frame 形式如下:
    stack:
    local_array: 1, 2
    PC-> 把变量a 从local_array 取出,然后压入堆栈
    把变量b 从local_array 取出,然后压入堆栈
    把堆栈顶部两个元素弹出后相加,把结果压入堆栈
    把堆栈顶部的元素弹出后返回。

    执行第一条语句后,stack frame 如下:
    stack: 1
    local_array: 2
    把变量a 从local_array 取出,然后压入堆栈
    PC-> 把变量b 从local_array 取出,然后压入堆栈
    把堆栈顶部两个元素弹出后相加,把结果压入堆栈
    把堆栈顶部的元素弹出后返回。

    继续执行当前PC指针指向的语句后,情况如下:
    stack: 2, 1
    local_array:
    把变量a 从local_array 取出,然后压入堆栈
    把变量b 从local_array 取出,然后压入堆栈
    PC-> 把堆栈顶部两个元素弹出后相加,把结果压入堆栈
    把堆栈顶部的元素弹出后返回。

    继续执行后情况如下:
    stack: 3
    local_array:
    把变量a 从local_array 取出,然后压入堆栈
    把变量b 从local_array 取出,然后压入堆栈
    把堆栈顶部两个元素弹出后相加,把结果压入堆栈
    PC-> 把堆栈顶部的元素弹出后返回。

    依次类推。

    你可以看到,java指令运行时,必须确保指令需要的参数全部放在栈上,指令执行后如果有结果,结果也同样会存在在堆栈上。

    有了上面的基本理论,我们可以看看上一节程序生成的java汇编代码:

    .class public CSourceToJava
    .super java/lang/Object
    
    .method public static main([Ljava/lang/String;)V
        getstatic   java/lang/System/out Ljava/io/PrintStream;
        ldc "Hello java virtual machine!"
        invokevirtual   java/io/PrintStream/println(Ljava/lang/String;)V
        return
    .end method
    .end class

    指令getstatic 的作用就是把out对象压入前面所说的堆栈中。ldc 的作用是把字符串压入到前面所说的堆栈中,这两句执行后,堆栈情况如下:
    stack:
    “Hello java virtual machine!”
    out

    invokevirtual 指令执行时,它会从堆栈顶部取出两个元素,接着从第二个元素,也就是out对象中,找到要调用的方法,也就是println, 然后把堆栈第一个元素作为参数,调用println方法,执行完毕后,如果有返回值,则把返回值给压入堆栈。

    理解了指令的执行原理后,其他语句完全可以跟下面的代码对应起来:

    public class CSourceToJava {
        public static void main(String[] args) {
            System.out.println("Hello java virtual machine!");
        }
    }

    由此语句.class public CSourceToJava对应的就是java代码中类的声明部分:
    public class CSourceToJava, 指令.method public static main 对应的就是静态函数main 的声明:public static void main。

    语句Ljava/io/PrintStream; 表示对象的类型,例如对象out 的类型是java.io.PrintStream。前面的L表示该对象是一个类,如果前面还有一个左中括号,那表示该对象是一个数组,例如[Ljava/lang/String; 表示的是String[].

    更详实讲解请参看视频。

    更多技术信息,包括操作系统,编译器,面试算法,机器学习,人工智能,请关照我的公众号:
    这里写图片描述

    展开全文
  • C语言程序中,很多函数并不是执行全部语句后,才从最底部返回的,大多数情况下,当某些条件成立时就可以通过return 语句立即返回,而无需执行接下来的代码,本节,我们继续增强java开发C语言解释器功能,使其...
  • 本节我们研究jvm的return指令,以及对局部变量的操作,同时给出了一段C语言代码,展示了我们的编译器是如何把给定C语言编译成java字节码的
  • 本文详细描述了如何把C语言中的printf函数调用编译成对应的java字节码
  • 本文介绍了java虚拟机所能运行的基础指令,同时讲解了虚拟机是如何基于堆栈和队列配合相关基础指令,...最后我们给出了一段C语言代码,并详细讲解了我们的编译器如何把代码编译成能在java虚拟机上执行的java汇编语言

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 456
精华内容 182
关键字:

java开发c语言编译器

java 订阅
c语言 订阅