c++ - C++ 的哪些功能使用了 JR 指令?

标签 c++ mips

MIPS 跳转寄存器 (JR) 指令经常出现在 C++ 代码的二进制文件中。那么,C++ 中的哪些功能使用了 JR 指令,为什么要使用这些指令?

最佳答案

分支指令只能用于目标地址在编译时已知并且在当前指令的小范围内的情况。您不能(轻松地)使用它来分支到一个静态未知的地址,并且必须在运行时计算/加载,或者跳转到太远的目标

下面是一些必须使用JRJALR 的例子(除了JALR 存储要返回的当前地址外,两者完全相同稍后):

  • 跳转到任意地址:静态分支指令不能用于跳转到 32 位或 64 位地址,因为立即数只有 16 或 26 位长。您需要将完整地址加载到寄存器中并使用 JR/JALR

    跳转
  • Function pointers : 调用函数只在运行时才知道,所以显然你需要一些方法来动态调用它

    int Add(int a, int b);
    int Sub(int a, int b);
    int Mul(int a, int b);
    int Div(int a, int b);
    int (*p[4]) (int x, int y) = { Add, Sub, Mul, Div };
    
    int test_function_pointer(int i, int x, int y) {
        return p[i](x, y);
    }
    

    shared libraries 中的函数(*.dll, *.so...) 在加载之前对进程也是未知的,因此如果您手动加载这些库(使用 LoadLibrary()dlopen() ...),您还将获得地址到一个函数指针,并用 JR/JALR 调用它们。通常情况下,函数将使用 JALR 调用,但如果它位于函数的末尾并且 tail-call optimization启用后将使用 JR

    Vtable在C++等很多OOP语言中也是函数指针的一个例子

    struct A {
        virtual int getValue() = 0;
    };
    
    int test_vtable(A *a) {
        return a->getValue() + 1;
    }
    

    Demo on Godbolt's Compiler Explorer

  • Jump table (就像在一个大开关 block 中)

    typedef int (*func)(int);
    
    int doSomething(func f, int x, int y)
    {
        switch(x)
        {
            case 0:
                return f(x + y);
            case 1:
                return f(x + 2*y);
            case 2:
                return f(2*x + y);
            case 3:
                return f(x - y);
            case 4:
                return f(3*x + y);
            case 5:
                return f(x * y);
            case 6:
                return f(x);
            case 7:
                return f(y);
            default:
                return 3;
        }
    }
    

    GCC 将上面的代码编译成

    doSomething(int (*)(int), int, int):
            sltu    $2,$5,8
            beq     $2,$0,$L2 # x >= 8: default case
            move    $25,$4
    
            lui     $2,%hi($L4)
            addiu   $2,$2,%lo($L4)  # load address of $L4 to $2
            sll     $5,$5,2         # effective address = $L4 + x*4
            addu    $5,$2,$5
            lw      $2,0($5)
            nop
            j       $2
            nop
    
    $L4:
            .word   $L11
            .word   $L5
            .word   $L6
            .word   $L7
            .word   $L8
            .word   $L9
            .word   $L10
            .word   $L11
    $L11:
            jr      $25
            move    $4,$6
    
    $L9:
            sll     $4,$6,2
            jr      $25
            addu    $4,$4,$6
    # ... many more cases below
    

    您可以在 Compiler Explorer 上看到完整的输出

    $L4 是一个跳转表,其中包含您要跳转到的位置的地址,也就是此代码段中的 case block 。它的地址存储在$2中,需要使用jr将指令指针移动到该地址。 j $2 如上所示,但我认为这是一个反汇编程序错误,因为 j 无法接收寄存器操作数。一旦你处于正确的情况下,jr 将再次用于调用 f 函数指针

另见 Necessity of J vs. JAL (and JR vs. JALR) in MIPS assembly

关于c++ - C++ 的哪些功能使用了 JR 指令?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58477929/

相关文章:

c++ - 使用 MinGW 的 -j(作业)选项构建 wxWidgets 时出错

c++ - 指向根的二叉树指针需要被引用和取消引用。为什么?

C 中的调用堆栈回溯

assembly - 如果数字不 >= 0,为什么代码加 7

MIPS:n选择k函数

c++ - 如何给用户分配一定的时间回答?

c++ - 如何 "Clear"一个WinAPI透明窗口

c++ - 定义变量与即时计算

arrays - 在 MIPS 中的特定内存地址声明数组

c - MIPS 程序集 - 数组?