从 Erts 中学的 C 技巧

在对Erlang运行时代码进行分析时,发现了一些C语言不常见的语法应用。

起因

因为对Erlang的OTP 17.0做了一段时间的代码分析,并且近期看到了大神写的书The Erlang Runtime System 。发现了Erts中的erl_emu.c的process_main有个C语言写法自己从没用过,就查阅了相关资 料。

跳转标签作为值1

简单说就是在函数内定义的标签,可以使用操作符‘&&’来进行取值,值的类型是void*,这 个值是一个定值,是不可以改变的。然后可以使用goto语句进行跳转。代码如下

1
2
3
4
5
6
7
void *ptr;
/* … */
ptr = &&foo;
goto *ptr;

static void *array[] = { &&foo, &&bar, &&hack };
goto *array[i];

在Erlang中,这种模式被用来完成Erlang的Beam指令流转,做了一个简单的模拟代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include<string.h>      
#include<stdlib.h>       
#include<stdio.h>                                                                                                      

typedef unsigned long Uint;  
typedef unsigned long  BeamInstr;   
typedef unsigned long  UWord;                                                                                          

#define OpCase(OpCode)    lb_##OpCode 
#define Goto(Rel) goto *((void *)Rel)   
#define OpCode(OpCode)  (&&lb_##OpCode)                                                                             
int main(){                                                
    BeamInstr* I;                                                                         
    BeamInstr* next; 
    BeamInstr beam_apply[2];   
     
    beam_apply[0]             = (BeamInstr) OpCode(i_apply);   
    beam_apply[1]             = (BeamInstr) OpCode(normal_exit);   
     
    printf("beam_apply %p\r\n",beam_apply);    
    printf("beam_apply[0] %p\r\n",beam_apply[0]);  
    printf("beam_apply[1] %p\r\n",beam_apply[1]);                                                                     
    I = (BeamInstr *) beam_apply;  
    next = (BeamInstr *) *I;   
    printf("next: %p\r\n",next);   
    Goto(next);
    OpCase(i_apply):{        
        printf("i_apply %p %p \r\n",I,(*I)); 
        I = I + 1;                          
        Goto(*I);                                                                           
    }
    OpCase(normal_exit):{  
        printf("normal_exit %p %p \r\n",I,(*I));  
        return 0;     
    }         
    return 1;          
}

beam_apply这个数组中存放的是被转化成整形数值的地址。赋值给next的时候,next类型是 指针,并且指向相应lb地址。 next = (BeamInstr *) *I; gcc会吧switch编译成jmp语句, 为什么还要使用这种费劲的方式而不使用switch呢?这是因为switch在jmp前需要进行一次 判断,而使用这种JUMP TABLE的模式是直接jmp到后面的地址。

参考文献