java开发C编译器:jvm函数调用时的参数传递

本文介绍了一种将C语言代码转换为Java字节码的方法,重点在于如何处理函数参数和局部变量,确保它们在Java虚拟机(JVM)的堆栈和队列中的正确映射。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

更详细的讲解和代码调试演示过程,请参看视频
如何进入google,算法面试技能全面提升指南

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

更详细的讲解和代码调试演示过程,请参看视频
Linux kernel Hacker, 从零构建自己的内核

jvm在执行代码时,必须围绕两个数据结构,一个是堆栈,一个是队列。当执行字节码指令时,如果指令需要处理数据,那么此时必须保证数据存储在堆栈顶部,如果堆栈上的数据需要转移存储,那么他们会被存储在队列上。

前面我们讲过,java函数的局部变量都是存储在队列上的,只有当代码需要处理这些变量时,才会从队列转移到堆栈上。在上一节我们把C代码转换成java字节码的例子中,C语言函数含有两个局部变量,这两个变量会对应于队列上的某两个元素,他们到底对应队列上哪个元素是可以由代码指定的,只要访问变量时,从队列的相应位置读取就可以,但有一种情况,局部变量与队列中的元素位置是一一对应的,这种情况就是函数的输入参数,例如函数:

static double lotsOfArguments(int a, long b, float c, String[][] d) {
    ....
}

当上面函数运行时,在执行函数lotsOfArguments前,jvm会把输入参数全部放到堆栈上,当函数被执行时,参数会从堆栈拷贝到局部变量队列,因此当lotsOfArguments执行前,堆栈上参数如下:
stack:
d
c
b
a
函数执行时堆栈上的参数会依次拷贝到局部变量队列,情况如下:
local_list: d c b a
也就是队列第0个元素存储参数d, 第一个元素存储参数c,第二个元素存储参数b,第三个元素存储参数a。基于这个特点,当C代码函数调用有参数传递时,编译器把代码编译成java字节码时,要注意上面所说的机制。

完成本节代码后,我们的编译器能将下面代码正确编译成java字节码

int f(int a, int b) {
    printf("value of a and b is : %d, %d", a, b);
    int c;
    c = a + b;
    return c;
}

void main() {
    int c; 
    c = f(1, 2);
    printf("result of calling f is :%d", c);
}

本节代码与上节相差不大,主要目的就是要确定函数的输入参数与jvm队列中元素的对应关系。

上面代码中,main函数调用函数f,输入参数是1,2。于是当f调用前,会把参数先压到堆栈上,情景如下:
stack:
2
1
因此f执行是,堆栈上的参数拷贝到局部变量队列时如下:
local_list: 2, 1
所以在函数f运行时,对变量a的读写,要对应到队列的第1个元素,对变量b的读写,要对应到队列的第0个元素。

被调函数f返回值是整形,同时有两个整形输入参数,因此编译成java字节码时,函数声明如下:
.method public static f(II)I
因此我们需要修改FunctDeclExecutor.java中的代码,以便生成上面的函数声明:

private String emitArgs(Symbol funSymbol) {
        Symbol s = funSymbol.getArgList();
        ArrayList<Symbol> params = new ArrayList<Symbol>();
        while (s != null) {
            params.add(s);
            s = s.getNextSymbol();
        }
        Collections.reverse(params);
        String args = "(";
        for (int i = 0; i < params.size(); i++) {
            Symbol symbol = params.get(i);
            String arg = "";
            if (symbol.getDeclarator(Declarator.ARRAY) != null) {
                arg += "[";
            }

            if (symbol.hasType(Specifier.INT)) {
                arg += "I";
            }

            args += arg;
        }

        if (funSymbol.hasType(Specifier.INT)) {
            args += ")I";
        } else {
            args += ")V";
        }

        return args;
    }

上面代码中,输入参数是函数f对应的符号对象,从该符号对象获得输入参数的符号对象,进而判断输入参数的类型,如果输入参数类型是整形的话,编译器就输出字符I, 同时获得函数返回值类型,如果返回值是整形,那么需要在函数声明的最后加上字符I.

接下来,我们需要确定输入参数a和b与局部变量队列的对应关系,相关实现代码如下programGenerator.java:

public int getLocalVariableIndex(Symbol symbol) {
        TypeSystem typeSys = TypeSystem.getTypeSystem();
        String funcName = nameStack.peek();
        Symbol funcSym = typeSys.getSymbolByText(funcName, 0, "main");
        ArrayList<Symbol> localVariables = new ArrayList<Symbol>();
        Symbol s = funcSym.getArgList();
        while (s != null) {
            localVariables.add(s);
            s = s.getNextSymbol();
        }

        ArrayList<Symbol> list = typeSys.getSymbolsByScope(symbol.getScope());
        for (int i = 0; i < list.size(); i++) {
            if (localVariables.contains(list.get(i)) == false) {
                localVariables.add(list.get(i));
            }
        }

        for (int i = 0; i < localVariables.size(); i++) {
            if (localVariables.get(i) == symbol) {
                return i;
            }
        }

        return -1;
    }

当编译器要确定变量a在局部队列中的位置时,把变量a的符号对象输入上面函数。该函数先通过nameStack获得当前正在执行的函数名,通过函数名找到函数对应的符号对象,进而找到函数对应的输入参数,例如通过函数f的符号对象就可以找到其输入参数的符号对象,因为输入参数的符号对象会以链表的方式存储在函数的符号对象中:
Symbol(f)->Symbol(b)->Symbol(a).
上面的代码先把参数b和a的符号对象拿到,存入到一个队列中:
localVariables:Symbol(b), Symbol(a).
然后再通过函数名f,找到所以作用域范围为f的变量的符号对象:
list: Symbol(b), Symbol(a), Symbol(c)
把存在list中,但不存在localVariables中的符号对象加入到localVariables队列,于是localVariables队列情况如下:
localVariables: Symbol(b), Symbol(a), Symbol(c)

要查找给定变量在局部队列的位置,只要把变量的符号对象拿到localVariables队列中查询就可以了,例如想获得变量a在局部队列中的位置,那么拿到变量a的Symbol对象,在localVariables队列中查询,得到结果1,就是输入参数a对应在局部变量队列中的位置。

上面代码运行后,把给定的C代码编译成java汇编代码时结果如下:

.class public CSourceToJava
.super java/lang/Object

.method public static main([Ljava/lang/String;)V
    sipush  1
    sipush  2
    invokestatic    CSourceToJava/f(II)I
    istore  0
    iload   0
    getstatic   java/lang/System/out Ljava/io/PrintStream;
    ldc "result of calling f is :"
    invokevirtual   java/io/PrintStream/print(Ljava/lang/String;)V
    istore  1
    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
.method public static f(II)I
    iload   1
    iload   0
    getstatic   java/lang/System/out Ljava/io/PrintStream;
    ldc "value of a and b is : "
    invokevirtual   java/io/PrintStream/print(Ljava/lang/String;)V
    istore  3
    getstatic   java/lang/System/out Ljava/io/PrintStream;
    iload   3
    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
    istore  3
    getstatic   java/lang/System/out Ljava/io/PrintStream;
    iload   3
    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
    iload   1
    iload   0
    iadd
    istore  2
    iload   2
    ireturn
.end method

.end class

把java汇编代码编译成二进制字节码运行后结果如下:
这里写图片描述

变量a的值打印出来结果是1,变量b的值打印出来后结果是2,也就是说,编译器把代码编译成java字节码中,读写输入参数时,能在局部变量队列中找到正确的对应元素。

更加详细的代码讲解和代码调试演示过程,请参看视频。

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值