Erlang数据类型的内部实现

来源:互联网 发布:婚纱摄影的网络邀约 编辑:程序博客网 时间:2024/05/22 13:40

Erlang中有8种基础数据类型(integer、 float、 atom、 reference、 fun、 port、 pid、 binary)和两种复合结构(tuple、list)。在erl编程时,对这些数据类的内存分配及引用都是透明的,了解erl数据类型的内部实现,可以让我们更优更合理的运用数据类型,更好地评估程序性能。


假设erl中有这么一组数据: 1、2、3、"a"、"b"、"c",
在erl虚拟机(vm)中将会这样描述erl数据:1F、2F、3F、3214B、4B6CB、1EDCB,
从上面可以看出,在vm内部,erl的数据类型是以后缀来识别的,比如小整数的后缀都为F,封装方法可用C表示为 (var << 4) | 0xF。
同时可以看出字符串的后缀都为B,但却不同与小整数的封装方法,数字串以及更多的数据类型究竟是如何封装的呢?我们继续往下看。


下面是一些erl数据 对应 在vm中的描述的示例。

表1@erlang@vm10x1F20x2F12345678900x1B0A37A12345678910x1B0A3A2-10xFFFFFFFF-20xFFFFFFEF[1,2]0x1B0BDE9[3,4]0x1B0BEA5"ab"0x1B0A19D"cb"0x1B0A1CD<<"ab">>0x1B0BFAE<<"cd">>0x1B0C06Arolong0x6BD0Bmytest0x6BD4B

以上看来,有些类型似乎不好找规律了,只好翻阅erlang源码,一探究竟。


erl_term.h中可以看到如下代码:


#define _TAG_PRIMARY_SIZE2#define _TAG_PRIMARY_MASK0x3#define TAG_PRIMARY_HEADER0x0#define TAG_PRIMARY_LIST0x1#define TAG_PRIMARY_BOXED0x2#define TAG_PRIMARY_IMMED10x3#define _TAG_IMMED1_SIZE4#define _TAG_IMMED1_MASK0xF#define _TAG_IMMED1_PID((0x0 << _TAG_PRIMARY_SIZE) | TAG_PRIMARY_IMMED1)#define _TAG_IMMED1_PORT((0x1 << _TAG_PRIMARY_SIZE) | TAG_PRIMARY_IMMED1)#define _TAG_IMMED1_IMMED2((0x2 << _TAG_PRIMARY_SIZE) | TAG_PRIMARY_IMMED1)#define _TAG_IMMED1_SMALL((0x3 << _TAG_PRIMARY_SIZE) | TAG_PRIMARY_IMMED1)#define _TAG_IMMED2_SIZE6#define _TAG_IMMED2_MASK0x3F#define _TAG_IMMED2_ATOM((0x0 << _TAG_IMMED1_SIZE) | _TAG_IMMED1_IMMED2)#define _TAG_IMMED2_CATCH((0x1 << _TAG_IMMED1_SIZE) | _TAG_IMMED1_IMMED2)#define _TAG_IMMED2_NIL((0x3 << _TAG_IMMED1_SIZE) | _TAG_IMMED1_IMMED2)

我们来简化一下代码,并将16进制数改为用2进制来表示,如下:

#define _TAG_PRIMARY_SIZE2#define _TAG_PRIMARY_MASK11B#define TAG_PRIMARY_HEADER00B#define TAG_PRIMARY_LIST01B#define TAG_PRIMARY_BOXED10B#define TAG_PRIMARY_IMMED111B#define _TAG_IMMED1_SIZE4#define _TAG_IMMED1_MASK1111B#define _TAG_IMMED1_PID0011B#define _TAG_IMMED1_PORT0111B#define _TAG_IMMED1_IMMED21011B#define _TAG_IMMED1_SMALL1111B#define _TAG_IMMED2_SIZE6#define _TAG_IMMED2_MASK111111B#define _TAG_IMMED2_ATOM001011B#define _TAG_IMMED2_CATCH011011B#define _TAG_IMMED2_NIL111011B


由上可看出,对这些类型进行了分层,每2位就一个层次,分别为primary、immed1、immed2。
知道了这些定义,对于erl数据类型的内部实现就已清楚了大半,以下举例说明。


例1:判断Erlang数据是否为list的方法

如果要判断一个erl数据是否为list,只需要判断最后两位是否为01,可描述为:(var & 11B) == 01B 
现在我们来验证一下表1中的一组的数据。
-----------------
[1,2] -> 0x1B0BDE9
[3,4] -> 0x1B0BEA5
-----------------
1B0BDE9和1B0BEA5的最后一位用2进制来表示,分别为:1001B,0101B
可见最后两位都为01,即为TAG_PRIMARY_LIST的值。


例2:检测Erlang数据类型的方法

假设vm中收到一个值为0xFFFFFFFB的erl变量,要想知道这是一个什么数据,可做如下分析。
0xFFFFFFFB的最后一个字节(0xFB)用2进制表示为:0x11111011B
首先判断TAG_PRIMARY,也就是最后两位,值为11B,对应了上面的定义TAG_PRIMARY_IMMED1。
目前只知道它是immed1类型, 要想进一步了解它是什么东西,只能继续判断前面两位(10B)。
现在知道最后四位为1011B,对应定义_TAG_IMMED1_IMMED2,即为immed2,仍然不明确它的类型,
只能再取前面两位(11B), 现在知道最后六位为111011B,对应_TAG_IMMED2_NIL,
说明它是一个nil数据,即为一个空的list(或者是list的末端,具体内容在以后文章中进一步阐述)。


例3:Erlang中的整型处理

在表1中:
-----------------
1 -> 0x1F
2 -> 0x2F
-----------------
整数1和2封装成erl数据分别为1F和2F,从这里我们很容易知道它们的封装和解封取值的过程,
将1F解封取出整数值,只需要右移四位(1F >> 4)即可。
但是对于大整数,这方法就不可取了,如:
-----------------------------------
1234567890 -> 0x1B0A37A
1234567891 -> 0x1B0A3A2
-----------------------------------
以例2的方法,可知0x1B0A37A和0x1B0A3A2是BOXED类型,0x1B0A37A和0x1B0A3A2去掉tag的值后,其实就是一个erl内部的数据指针。
可见erl内部将整数分为小整型和大整型, 但对于erl程编来说,它是透明的。


例4:Erlang中的字符串实现方式

------------------------
"ab" -> 0x1B0A19D
"cb" -> 0x1B0A1CD
------------------------
以例1的方法,可以知道0x1B0A19D和0x1B0A1CD均是list类型,说明erl中的字符串是用list实现的。
和例3中的大整数类似,0x1B0A19D和0x1B0A1CD去掉tag值以后,就是指向字符串的指针值。


以上举了几个典型的例子,其它数据类型的内部实现类似。这里要进一步说明的就是关于TAG_PRIMARY_HEADER,暂称为header类型,顾名思议,它常用作为一些类据结构及数据类型的头部,用于表示数据长度或一些其它参量。

在源码中有一段关于header的注释:
/* * HEADER representation: * *aaaaaaaaaaaaaaaaaaaaaaaaaatttt00arity:26, tag:4 * * HEADER tags: * *0000ARITYVAL *      0001    BINARY_AGGREGATE                | *001xBIGNUM with sign bit| *0100REF| *0101FUN| THINGS *0110FLONUM| *      0111    EXPORT                          | *1000REFC_BINARY|| *1001HEAP_BINARY| BINARIES| *1010SUB_BINARY|| *      1011    Not used *      1100    EXTERNAL_PID  |                 | *      1101    EXTERNAL_PORT | EXTERNAL THINGS | *      1110    EXTERNAL_REF  |                 | *      1111    Not used * * COMMENTS: * * - The tag is zero for arityval and non-zero for thing headers。 * - A single bit differentiates between positive and negative bignums。 * - If more tags are needed, the REF and and EXTERNAL_REF tags could probably *   be combined to one tag。 * * XXX: globally replace XXX_SUBTAG with TAG_HEADER_XXX */

在前面例子中我们可以知道,在vm中,像大整数、tuple等类似的数据在传递时,只是传递了一个被封装过的类型为boxed的指针值,要想进一步了解指针所指的内容结构,
首先要取出内容中的开头的4个字节,即为header值。
现在我们假设有一个header值为0xC0(11000000B),从上面注释中可以得知,尾部为000000B的header对应的header tag为arityval,
关于header更具体的说明,还要参阅以下代码及注释。

#define ARITYVAL_SUBTAG(0x0 << _TAG_PRIMARY_SIZE) /* TUPLE */#define BIN_MATCHSTATE_SUBTAG(0x1 << _TAG_PRIMARY_SIZE) #define POS_BIG_SUBTAG(0x2 << _TAG_PRIMARY_SIZE) /* BIG: tags 2&3 */#define NEG_BIG_SUBTAG(0x3 << _TAG_PRIMARY_SIZE) /* BIG: tags 2&3 */#define _BIG_SIGN_BIT(0x1 << _TAG_PRIMARY_SIZE)#define REF_SUBTAG(0x4 << _TAG_PRIMARY_SIZE) /* REF */#define FUN_SUBTAG(0x5 << _TAG_PRIMARY_SIZE) /* FUN */#define FLOAT_SUBTAG(0x6 << _TAG_PRIMARY_SIZE) /* FLOAT */#define EXPORT_SUBTAG(0x7 << _TAG_PRIMARY_SIZE) /* FLOAT */#define _BINARY_XXX_MASK(0x3 << _TAG_PRIMARY_SIZE)#define REFC_BINARY_SUBTAG(0x8 << _TAG_PRIMARY_SIZE) /* BINARY */#define HEAP_BINARY_SUBTAG(0x9 << _TAG_PRIMARY_SIZE) /* BINARY */#define SUB_BINARY_SUBTAG(0xA << _TAG_PRIMARY_SIZE) /* BINARY */#define EXTERNAL_PID_SUBTAG(0xC << _TAG_PRIMARY_SIZE) /* EXTERNAL_PID */#define EXTERNAL_PORT_SUBTAG(0xD << _TAG_PRIMARY_SIZE) /* EXTERNAL_PORT */#define EXTERNAL_REF_SUBTAG(0xE << _TAG_PRIMARY_SIZE) /* EXTERNAL_REF */#define _TAG_HEADER_ARITYVAL(TAG_PRIMARY_HEADER|ARITYVAL_SUBTAG)#define _TAG_HEADER_FUN(TAG_PRIMARY_HEADER|FUN_SUBTAG)#define _TAG_HEADER_POS_BIG(TAG_PRIMARY_HEADER|POS_BIG_SUBTAG)#define _TAG_HEADER_NEG_BIG(TAG_PRIMARY_HEADER|NEG_BIG_SUBTAG)#define _TAG_HEADER_FLOAT(TAG_PRIMARY_HEADER|FLOAT_SUBTAG)#define _TAG_HEADER_EXPORT(TAG_PRIMARY_HEADER|EXPORT_SUBTAG)#define _TAG_HEADER_REF(TAG_PRIMARY_HEADER|REF_SUBTAG)#define _TAG_HEADER_REFC_BIN(TAG_PRIMARY_HEADER|REFC_BINARY_SUBTAG)#define _TAG_HEADER_HEAP_BIN(TAG_PRIMARY_HEADER|HEAP_BINARY_SUBTAG)#define _TAG_HEADER_SUB_BIN(TAG_PRIMARY_HEADER|SUB_BINARY_SUBTAG)#define _TAG_HEADER_EXTERNAL_PID  (TAG_PRIMARY_HEADER|EXTERNAL_PID_SUBTAG)#define _TAG_HEADER_EXTERNAL_PORT (TAG_PRIMARY_HEADER|EXTERNAL_PORT_SUBTAG)#define _TAG_HEADER_EXTERNAL_REF  (TAG_PRIMARY_HEADER|EXTERNAL_REF_SUBTAG)#define _TAG_HEADER_BIN_MATCHSTATE (TAG_PRIMARY_HEADER|BIN_MATCHSTATE_SUBTAG)#define _TAG_HEADER_MASK0x3F#define _HEADER_SUBTAG_MASK0x3C/* 4 bits for subtag */#define _HEADER_ARITY_OFFS6

从上面代码的第一行可以得知,header值为11000000B的数据是一个tuple,它的长度为11000000B >> 6 == 11B,即是一个长度为3的tuple。

END,此文可作为阅读Erlang C源码的入门知识,对于erl编程和nif的开发及理解也有一定帮助,关于其它数据类型及数据结构的具体实现,有兴趣的同学可以参阅$ERL_TOP/erts/emulator/beam/erl_term.h


Erlang中list和tuple的构建及转换的内部实现