Erlang数据类型的内部实现

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@vm
10x1F
20x2F
12345678900x1B0A37A
12345678910x1B0A3A2
-10xFFFFFFFF
-20xFFFFFFEF
[1,2]0x1B0BDE9
[3,4]0x1B0BEA5
"ab"0x1B0A19D
"cb"0x1B0A1CD
<<"ab">>0x1B0BFAE
<<"cd">>0x1B0C06A
rolong0x6BD0B
mytest0x6BD4B


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


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


#define _TAG_PRIMARY_SIZE	2
#define _TAG_PRIMARY_MASK	0x3
#define TAG_PRIMARY_HEADER	0x0
#define TAG_PRIMARY_LIST	0x1
#define TAG_PRIMARY_BOXED	0x2
#define TAG_PRIMARY_IMMED1	0x3


#define _TAG_IMMED1_SIZE	4
#define _TAG_IMMED1_MASK	0xF
#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_SIZE	6
#define _TAG_IMMED2_MASK	0x3F
#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_SIZE	2
#define _TAG_PRIMARY_MASK	11B
#define TAG_PRIMARY_HEADER	00B
#define TAG_PRIMARY_LIST	01B
#define TAG_PRIMARY_BOXED	10B
#define TAG_PRIMARY_IMMED1	11B


#define _TAG_IMMED1_SIZE	4
#define _TAG_IMMED1_MASK	1111B
#define _TAG_IMMED1_PID		0011B
#define _TAG_IMMED1_PORT	0111B
#define _TAG_IMMED1_IMMED2	1011B
#define _TAG_IMMED1_SMALL	1111B


#define _TAG_IMMED2_SIZE	6
#define _TAG_IMMED2_MASK	111111B
#define _TAG_IMMED2_ATOM	001011B
#define _TAG_IMMED2_CATCH	011011B
#define _TAG_IMMED2_NIL		111011B


由上可看出,对这些类型进行了分层,每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:
 *
 *	aaaaaaaaaaaaaaaaaaaaaaaaaatttt00	arity:26, tag:4
 *
 * HEADER tags:
 *
 *	0000	ARITYVAL
 *      0001    BINARY_AGGREGATE                |
 *	001x	BIGNUM with sign bit		|
 *	0100	REF				|
 *	0101	FUN				| THINGS
 *	0110	FLONUM				|
 *      0111    EXPORT                          |
 *	1000	REFC_BINARY	|		|
 *	1001	HEAP_BINARY	| BINARIES	|
 *	1010	SUB_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_MASK	0x3F
#define _HEADER_SUBTAG_MASK	0x3C	/* 4 bits for subtag */
#define _HEADER_ARITY_OFFS	6

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

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


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

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值