java开发C语言编译器:jvm的return指令以及局部变量的操作

本文详细介绍了一种将C语言代码转换为Java字节码的方法,重点介绍了如何处理局部变量和函数返回值,并展示了实际的代码示例。

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

请结合视频阅读本文以便获得更清晰的理解。
更详细的讲解和代码调试演示过程,请参看视频
用java开发C语言编译器

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

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

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

jvm运行字节码时,代码的运行必须围绕两种数据结构,一种是堆栈,一种是队列,如果jvm执行某条指令时,该指令需要对数据进行操作,那么被操作的数据在指令执行前,必须要压倒堆栈上。如果堆栈上的数据需要暂时保持起来时,它就会被加载到局部变量队列上。

java代码中,每个方法里面的局部变量包括函数的输入参数都会存储在队列上。我们看下面一个方法:

int f() {
    int a;
    int b;
    a = 1;
    b = 2;
}

jvm在执行上面代码时,首先会分配一个堆栈和一个队列,一开始堆栈和队列都为空:

stack: null
list: null

要执行语句 a = 1; 时,首先需要把常量1压到堆栈上:

stack: 1

局部变量a对应于队列的第0个元素,把1赋值给变量a,就相当于把堆栈顶部的数值1转移到队列的第0个元素,因此语句a =1;执行后队列和堆栈的情况如下:
stack: null
list: 1,

执行第二条语句b = 2; 时同理,先把常量2压到堆栈上:
stack: 2
list: 1

由于变量b是函数的第二个局部变量,因此它对应队列的第1个元素,把常量2赋值给变量b,就需要把堆栈顶部的数值2移到堆栈的第1个元素:

stack:
list: 1, 2

由此,当我们把C语言编译成java字节码时,在解析函数时,函数中的局部变量都需要对应到虚拟机局部变量队列中的对应元素,在一会给出的例子中,我们会通过代码看看,在解析C语言函数的局部变量时,程序是如何把变量和jvm的变量队列对应起来的。

在此,我们还需要介绍的jvm的return指令, 无论是C代码还是java代码,一旦函数有返回值时,都需要通过关键字return把数据返回给函数的调用者,代码中的return语句在编译成java字节码后,对应着多条语句。如果return 语句返回的数据类型是整形,那么该return语句对应的字节码指令是ireturn, 前面的i表示整形,同理,如果return 返回的数据类型是浮点数,那么对应的java字节码指令就是freturn, 前面的f表示float,如果return 返回的数据类型是double,对应的字节码指令就是dreturn, 前面的d表示double。

需要注意的是return 语句对应的字节码指令必须跟函数的返回值声明相一致,如果函数声明时返回的数据类型是整形,结果函数编译后使用的return指令是freturn,也就是要返回一个浮点数,这种不一致性会被jvm检测到,一旦发现指令的逻辑不一致,虚拟机就会拒绝执行给定的代码。

介绍完理论后,我们看看如何把理论付诸实践。下面的代码将是我们要编译成java字节码的C语言代码:

int f() {
    int a;
    int b;
    a = 1;
    b = 2;

    return a+b;
}

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

函数f的返回值是int,因此在编译成java字节码时,f 里面的return语句编译后要对应上jvm的ireturn指令,同时f中含有两个局部变量a,b,根据前面讲述的理论,在解析到这两个变量时,编译器需要把他们对应到java虚拟机中局部变量队列的相应元素。

我们看看,局部变量是如何对应到虚拟机队列的相应元素的,在ProgramGenerator.java中,添加代码如下:

public class ProgramGenerator extends CodeGenerator {
    private static ProgramGenerator instance = null;
    private  String funcName = "";
    ....
    public int getLocalVariableIndex(Symbol symbol) {
        TypeSystem typeSys = TypeSystem.getTypeSystem();
        ArrayList<Symbol> list = typeSys.getSymbolsByScope(symbol.getScope());
        Collections.reverse(list);
        for (int i = 0; i < list.size(); i++) {
            if (list.get(i) == symbol) {
                return i;
            }
        }

        return -1;
    }
    ....
}

每个变量都对应着一个符号对象Symbol, 同时每个变量都有给定的作用范围,getSymbolsByScope把同一作用范围内的变量全部取出来,形成一个队列,例如在f中,有两个具备变量a,b , 他们的作用范围都是f, 如果把a对应的Symbol对象传入上面函数后,list会得到一个队列,该队列包含两个Symbol对象,这两个对象就是变量a和b所对应的Symbol对象。得到这个队列后,我们通过变量符号对象在队列中的位置来对应他们在虚拟机队列中的位置。

第二处需要改动的代码在UnaryNodeExecutor.java中:

public class UnaryNodeExecutor extends BaseExecutor implements IExecutorReceiver{
    private Symbol structObjSymbol = null;
    private Symbol monitorSymbol = null;

    @Override
    public Object Execute(ICodeNode root) {
        executeChildren(root);
        ....
        switch (production) {
        ....
        case CGrammarInitializer.Name_TO_Unary:
            symbol = (Symbol)root.getAttribute(ICodeKey.SYMBOL);
            if (symbol != null) {
                root.setAttribute(ICodeKey.VALUE, symbol.getValue());
                root.setAttribute(ICodeKey.TEXT, symbol.getName());

                ICodeNode func = CodeTreeBuilder.getCodeTreeBuilder().getFunctionNodeByName(symbol.getName());
                if (func == null && symbol.getValue() != null) {
                    ProgramGenerator generator = ProgramGenerator.getInstance();
                    int idx = generator.getLocalVariableIndex(symbol);
                    generator.emit(Instruction.ILOAD, "" + idx);    
                }

            }
            break;
            .....
            }
            ....
    }
    .....
}

在解析到某个变量是,我们先看该变量是否已经赋值了,也就是symbol.getValue()返回值不是null, 如果赋值了,那么通过调用getLocalVariableIndex得到该变量在遍历队列里的位置,这个位置将作为它对应在虚拟机变量队列里的位置。假设该变量对应的位置是x, 那么上面代码将输出指令:
iload x
也就是把变量队列中第x个元素加载到堆栈顶端。

第三处需要改动的代码在FunctDeclExecutor.java:

public class FunctDeclExecutor extends BaseExecutor {
    private ArrayList<Object> argsList = null;
    private ICodeNode currentNode;
    ProgramGenerator generator = ProgramGenerator.getInstance();
    ....
    private String emitArgs(Symbol funSymbol) {
        argsList = FunctionArgumentList.getFunctionArgumentList().getFuncArgList(true);
        String args = "(";
        for (int i = 0; i < argsList.size(); i++) {
            Symbol symbol = (Symbol)argsList.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;
    }
    .....
}

这里我们要把C语言中的函数声明编译成java字节码的函数声明,原来我们一直默认函数返回值都是void型,现在我们函数可以返回整形了,一个函数本质上也是一个变量,因此函数f也对应着一个Symbol对象,我们通过判断该Symbol对象的类型就可以得知函数的返回值,以例子代码为例: int f() 由于f前面有关键字int来修饰,因此f对应的Symbol对象它包含一个类型为int的specifier,一旦我们判断到返回值是整形时,在把函数声明编译成字节码时,需要在函数参数列表后面加上一个I,用于表明返回值是整形,由此C代码中的函数声明int f()编译成java字节码后对应的代码为:
.method public static f()I。

第四处需要修改的代码还是在UnaryNodeExecutor.java中:

public class UnaryNodeExecutor extends BaseExecutor implements IExecutorReceiver{
....
   public Object Execute(ICodeNode root) {
   ....
   case CGrammarInitializer.Unary_LP_RP_TO_Unary:
   case CGrammarInitializer.Unary_LP_ARGS_RP_TO_Unary:
   ....
   emitReturnInstruction(symbol);
   ....
   }

  private void emitReturnInstruction(Symbol symbol) {
        if (symbol.hasType(Specifier.INT)) {
            ProgramGenerator.getInstance().emit(Instruction.IRETURN);
        } else {
            ProgramGenerator.getInstance().emit(Instruction.RETURN);
        }
    }

前面我们提到过,函数声明时,指明了返回值类型的话,那么return必须根据返回值类型对应到字节码相应的xreturn语句,在这里我们通过函数的符号对象,获得函数的返回值类型,如果函数的返回值类型是整形,那么编译器就要输出ireturn语句,如果返回值是void类型,那么输出return指令就可以了。

第五处需要修改的是Symbol.java:

public class Symbol implements IValueSetter{
....
public void setValue(Object obj) {
        if (obj != null) {
            System.out.println("Assign Value of " + obj.toString() + " to Variable " + name);   
        }

        this.value = obj;

        if (this.value != null) {
            ProgramGenerator generator = ProgramGenerator.getInstance();
            int idx = generator.getLocalVariableIndex(this);

            generator.emit(Instruction.ISTORE, "" + idx);   
        }

    }
    ....
}

在变量被赋值时,上面的代码会被调用,前面我们讲过,如果给局部变量赋值,局部变量对应的是虚拟机队列中的某个元素,对其赋值,相当于把堆栈顶部的数据转移到队列的对应位置上,假设例子中变量b对应在队列中的位置为1,上面代码执行后,编译器会输出指令:
istore 1

也就是把堆栈顶部的整形数值转移给队列中的第一个元素。

上面代码执行后,我们的编译器会把给定的C语言程序编译成如下java汇编代码:

.class public CSourceToJava
.super java/lang/Object

.method public static main([Ljava/lang/String;)V
    invokestatic    CSourceToJava/f()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  2
    getstatic   java/lang/System/out Ljava/io/PrintStream;
    iload   2
    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()I
    sipush  1
    istore  0
    sipush  2
    istore  1
    iload   0
    iload   1
    iadd
    ireturn
.end method

.end class

上面汇编代码编译成字节码后执行,其结果如下:
这里写图片描述

通过执行结果可见,我们编译器对代码的编译结果应该是正确的。由于本节将是内容有点抽象,请结合视频演示一起来阅读本文,以便获得更清晰的理解。

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

<think>好的,我现在需要详细解释用户提供的JNI函数Java_com_isa_navi_jni_hmi_HmiProxyJNI_InitializeCpp的每一行代码。首先,我需要回顾JNI的基本概念和常见操作,然后逐行分析代码的功能和可能的作用。 首先,函数声明是extern "C" JNIEXPORT void JNICALL,这表示这是一个C风格的导出函数,遵循JNI调用约定。函数名符合JNI命名规范,由Java包名、类名和方法名组成,这里是Java_com_isa_navi_jni_hmi_HmiProxyJNI_InitializeCpp,对应Java层的com.isa.navi.jni.hmi.HmiProxyJNI类的InitializeCpp方法。 接下来看函数体: 第一行是lantu::isa::WriteAndroidLog(2, "Java_com_isa_navi_jni_hmi_HmiProxyJNI_InitializeCpp"); 这应该是一个日志输出函数,参数2可能是日志级别(比如INFO或DEBUG),第二个参数是日志内容,用于记录该函数被调用的信息。 然后是一个if语句:if(Update::getinstance()->javaVm == nullptr)。这里检查Update单例实例中的javaVm成员是否为nullptr。如果为null,说明还未初始化JavaVM和相关对象,需要执行初始化流程。 接下来,声明JavaVM* javaVm; 用于存储获取到的JavaVM指针。然后jint ret = env->GetJavaVM(&javaVm); 调用JNIEnv的GetJavaVM方法,获取当前Java虚拟机的JavaVM指针,结果存入ret。根据注释,JNI_OK等于0,所以如果ret不等于JNI_OK(即非0),说明获取失败,直接return退出函数。 然后jclass cls = env->FindClass("com/isa/navi/jni/hmi/HmiJNIImpl"); 这里通过env的FindClass方法查找Java类com.isa.navi.jni.hmi.HmiJNIImpl的jclass引用。如果查找失败(返回NULL),则return退出。 接下来jmethodID jmid = env->GetStaticMethodID(cls, "getInstance", "()Lcom/isa/navi/jni/hmi/HmiJNIImpl;"); 获取HmiJNIImpl类的静态方法getInstance的方法ID。方法签名是"()Lcom/isa/navi/jni/hmi/HmiJNIImpl;",表示无参数,返回类型是HmiJNIImpl对象。如果获取失败(jmid为NULL),return退出。 然后jobject carobj = env->CallStaticObjectMethod(cls, jmid); 调用HmiJNIImpl类的静态方法getInstance,获取该类的单例实例对象carobj。如果调用结果为NULL(carobj为NULL),则return退出。 接下来jobject carObj = env->NewGlobalRef(carobj); 将局部引用carobj转换为全局引用carObj。因为局部引用在本地方法返回后会被JVM自动释放,而全局引用可以在多个本地方法调用中持续使用,所以这里需要创建全局引用以便后续使用。 最后,将获取到的javaVm和carObj存入Update单例实例的对应成员变量:Update::getinstance()->javaVm = javaVm; Update::getinstance()->carObj = carObj; 这样后续其他地方可以通过Update实例访问JavaVM和HmiJNIImpl的全局引用。 需要注意每一步的错误处理,比如获取JavaVM失败、查找类失败、获取方法ID失败、调用方法返回空等情况,都会直接return,避免后续操作出现空指针异常。整个函数的目的是初始化Update实例中的JavaVM和HmiJNIImpl全局引用,确保后续JNI调用可以正确访问Java层的对象和方法。 </think>以下是对 `Java_com_isa_navi_jni_hmi_HmiProxyJNI_InitializeCpp` 函数每一行代码的详细解释: --- ### **第1行:函数声明** ```cpp extern "C" JNIEXPORT void JNICALL Java_com_isa_navi_jni_hmi_HmiProxyJNI_InitializeCpp(JNIEnv *env, jobject thiz) ``` - `extern "C"`:告知编译器按C语言规则编译此函数(避免C++名称修饰,确保Java层能通过JNI正确找到该函数)。 - `JNIEXPORT`/`JNICALL`:JNI标准宏,标记函数为导出的JNI接口(由JVM调用)。 - 函数名 `Java_com_isa_navi_jni_hmi_HmiProxyJNI_InitializeCpp`:符合JNI命名规范,格式为 `Java_<包名>_<类名>_<方法名>`,对应Java层 `com.isa.navi.jni.hmi.HmiProxyJNI` 类的 `InitializeCpp` 方法。 - 参数 `env`(`JNIEnv*`):JNI接口指针,用于本地代码与Java层交互;`thiz`(`jobject`):调用此方法的Java对象实例(即Java层的 `this`)。 --- ### **第2行:日志记录** ```cpp lantu::isa::WriteAndroidLog(2, "Java_com_isa_navi_jni_hmi_HmiProxyJNI_InitializeCpp"); ``` - 调用自定义日志函数 `WriteAndroidLog`,参数 `2` 可能表示日志级别(如 `INFO`),第二个参数是日志内容,用于记录该函数被调用的信息(便于调试和追踪调用链路)。 --- ### **第3行:检查JavaVM是否已初始化** ```cpp if(Update::getinstance()->javaVm == nullptr) ``` - 检查单例类 `Update` 的实例中 `javaVm` 成员是否为 `nullptr`(未初始化)。若未初始化,则执行后续初始化逻辑;若已初始化(非空),则跳过整个 `if` 块。 --- ### **第4-5行:获取JavaVM指针** ```cpp JavaVM* javaVm; jint ret = env->GetJavaVM(&javaVm); ``` - `JavaVM* javaVm`:声明一个指针,用于存储Java虚拟机(JVM)的全局接口。 - `env->GetJavaVM(&javaVm)`:通过 `JNIEnv` 指针 `env` 调用 `GetJavaVM` 方法,将JVM的全局接口指针存入 `javaVm`。此方法用于从当前线程的 `JNIEnv` 获取全局的 `JavaVM`(后续其他线程可通过 `JavaVM` 附加到JVM并获取 `JNIEnv`)。 --- ### **第6行:检查获取JavaVM是否成功** ```cpp if (ret != JNI_OK) return; // JNI_OK=0 ``` - `ret` 是 `GetJavaVM` 的返回值,`JNI_OK` 是JNI定义的成功状态码(值为0)。若 `ret != JNI_OK`(获取失败),直接返回,避免后续操作因无有效 `JavaVM` 而崩溃。 --- ### **第7-8行:查找Java类** ```cpp jclass cls = env->FindClass("com/isa/navi/jni/hmi/HmiJNIImpl"); if (cls == NULL) return; ``` - `env->FindClass(...)`:通过 `JNIEnv` 查找Java类 `com.isa.navi.jni.hmi.HmiJNIImpl`,返回其 `jclass` 引用(表示Java类的元数据)。 - 若 `cls` 为 `NULL`(类未找到,可能是包名错误或类未加载),直接返回,避免后续操作使用无效类。 --- ### **第9-10行:获取静态方法ID** ```cpp jmethodID jmid = env->GetStaticMethodID(cls, "getInstance", "()Lcom/isa/navi/jni/hmi/HmiJNIImpl;"); if (jmid == NULL) return; ``` - `env->GetStaticMethodID(...)`:在 `cls`(`HmiJNIImpl` 类)中获取静态方法 `getInstance` 的方法ID。参数依次为:类引用、方法名、方法签名。 - 方法签名 `"()Lcom/isa/navi/jni/hmi/HmiJNIImpl;"` 表示:无参数(`()`),返回类型为 `com.isa.navi.jni.hmi.HmiJNIImpl` 对象(`L...;` 格式)。 - 若 `jmid` 为 `NULL`(方法不存在或签名错误),直接返回,避免调用无效方法。 --- ### **第11-12行:调用静态方法获取实例** ```cpp jobject carobj = env->CallStaticObjectMethod(cls, jmid); if (carobj == NULL) return; ``` - `env->CallStaticObjectMethod(...)`:调用 `HmiJNIImpl` 类的静态方法 `getInstance`(通过 `jmid` 定位),获取该类的单例实例对象,结果存入 `carobj`(局部引用)。 - 若 `carobj` 为 `NULL`(方法调用失败或返回 `null`),直接返回,避免后续使用无效对象。 --- ### **第13行:创建全局引用** ```cpp jobject carObj = env->NewGlobalRef(carobj); ``` - `env->NewGlobalRef(carobj)`:将局部引用 `carobj` 转换为全局引用 `carObj`。局部引用仅在当前JNI方法调用中有效(可能被JVM自动回收),而全局引用可跨线程、跨JNI方法长期持有,确保 `Update` 实例后续能持续访问该对象。 --- ### **第14-15行:保存JavaVM和全局引用到Update实例** ```cpp Update::getinstance()->javaVm = javaVm; Update::getinstance()->carObj = carObj; ``` - 将获取到的 `JavaVM` 指针和 `HmiJNIImpl` 全局引用 `carObj` 存入单例类 `Update` 的实例中。后续其他JNI方法可通过 `Update::getinstance()` 访问这些成员,与Java层交互(如跨线程调用Java方法)。 --- ### **总结** 此函数的核心目标是**初始化并保存JavaVM和HmiJNIImpl全局引用**,确保后续JNI操作(尤其是跨线程场景)能正确访问Java层对象。代码通过多层错误检查(如获取JavaVM失败、类未找到、方法不存在等)避免空指针异常,保证鲁棒性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值