繁体   English   中英

跳转表/分支与取消引用函数指针是否一样?

[英]Is a jump table/branch the same thing as dereferencing a function pointer?

我一直在尝试了解确切的跳转表,但在理解某些内容时遇到了麻烦。 从我看到的许多示例中,它们似乎可以归结为这一点,或者至少这是它的一个版本:

void func1() {};
void func2() {};
void func3() {};

int main()
{
    void(*jumpTo[3])(void) = { func1, func2, func3 };
    jumpTo[1]();

    return 0;
}

它们似乎只是一个函数指针数组,由某个值/位置索引。 那么跳转表只是索引一个函数指针数组是这种情况吗? 我真的对此感到很好奇,因为我看到很多人说switch语句通常作为性能指标被编译到跳转表中。 据我了解,通过以这种方式跳转到函数,它涉及到指针取消引用和函数调用。 我认为这两个都不是很好的表现。

该站点上的另一个答案表示,通过这种方式“您将添加switch语句不一定具有的函数调用开销”。 编译为跳转表的开关如何避免函数调用?

另外,在这里获得高度投票的答案是:“跳转表可以是指向函数的指针数组,也可以是机器码跳转指令的数组。” 您将如何跳转到机器代码指令而不是取消引用指针? 这样更快吗?

在我上面的示例中,指针之间是否存在差异,因为指针可以静态绑定,所以不必取消引用吗? 而不是在运行时传递随机数作为索引?

谢谢。

跳转表和函数表基本相同-地址数组。 跳转表包含goto目标的地址。 两者之间的唯一区别是跳转的方式。 当一个函数被调用时,返回地址被压入堆栈,因此当函数终止时它可以返回。

以下是跳转表的示例:

#include <stdio.h>
int main(int argc, char *argv[])
{
    switch (argc)
    {
        case 1: 
             printf("You provided no arguments.");
             break;
        case 2: 
             printf("You provided one argument.");
             break;
        case 3: 
             printf("You provided two arguments.");
             break;
        case 4: 
             printf("You provided three arguments.");
             break;
        case 5: 
             printf("You provided four arguments.");
             break;
        case 6: 
             printf("You provided five arguments.");
             break;
        default: 
             printf("You provided %d arguments.", argc-1);
             break;
    }
    return 0;
}

编译为:

    cmp edi, 6 ;Bounds check
    ja  .L2    ;jump to default branch
    mov eax, edi
    jmp [QWORD PTR .L4[0+rax*8]]
.L4:
    .quad   .L2 ;case 0 (same as default!!!)
    .quad   .L3 ;case 1
    .quad   .L5 ;case 2
    .quad   .L6 ;case 3
    .quad   .L7 ;case 4
    .quad   .L8 ;case 5
    .quad   .L9 ;case 6

主要区别在于对于跳转表,通常可以使用相对于程序计数器的寻址,这样该表将不需要任何重定位,并且可以位于.text节(或其他不可写和共享的节)中)。 这是因为典型的跳转表仅在同一目标文件中的很少位置使用,并且所有偏移量对于汇编程序都是已知的。

如果您有一个函数指针数组,则您需要以某种方式产生实际的指针,并且需要某种形式的重定位。

第二个可能性,跳转指令的阵列,是不是真的仅限于跳转指令本身 重要的是,所有目标指令序列(最后一条除外)的长度均相同,因此可以轻松计算出要跳转的偏移量。 这样,就不需要跳转表,但是它确实需要确切的指令宽度(和计数)信息,这很难在大多数目标上保证(RISC体系结构在加载常量时可能难以预测有效的指令计数) )。 这意味着在实践中,此方法仅限于针对目标的非常特殊形式的跳转指令。

通常,术语“跳转表”是指一种技术,其中有两个以上的分支/跳转目标,并且通过变量以一种方式或另一种方式计算表中的位置来通过变量选择跳转/分支目标。 本质上,您提供的示例:

void(*jumpTo[3])(void) = { func1, func2, func3 };
jumpTo[1]();

总体而言,是使用跳转表-不仅是对函数指针的取消引用。

C也提供了其他机制-例如,开关案例经常被编译到跳转表中,尤其是在案例值的范围很窄且之间没有间隙的情况下。 GCC作为非标准扩展提供的另一种机制是使用goto标签作为带有计算的goto指针值

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM