Erlang/Elixir 从 Erts 中学的 C 技巧

DavidAlphaFox · 发布于 2017年12月23日 · 237 次阅读
84794b

起因

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

跳转标签作为值

原文在这个地方。

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

void *ptr;
/* … */
ptr = &&foo;
goto *ptr;

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

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

#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到后面的地址。

暂无回复。
需要 登录/注册 后方可回复, 如果你还没有账号请点击这里 注册