从 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到后面的地址。
参考文献