动态类型 vs. 静态类型
众所周知Common Lisp是一种动态类型语言,
虽然编译器会在编译时检查类型, 但这种检查往往出于编译优化的目的,而不是为了类型安全的严格检查。
至于动态类型,喜欢的人喜欢它,赞扬它可以让代码简洁,开发高效, 厌恶的人厌恶它,批评它不够安全,无法协同工作构建复杂系统。
这也正是是动态类型语言的优美和邪恶的地方。
在Common Lisp里, 你可以通过declare/declaim的type/ftype子句在代码中声明值(对象)类型,也可以通过the来指定值(对象)类型(declare/declaim无非是the的语法糖),
但Common Lisp并不保证如果你在运行时给出一个非该类型的值(对象)会发生什么,行为是undefined。
另外,Common Lisp也不会作型别推倒, 函数的参数的类型无法自动的限定返回值类型。
尽管如此, 一般CL的最佳实践还是建议大家指明所使用的类型, 这样可能会得到更快的运行代码。
值(对象)的类型
很久前收藏的一幅Common Lisp的类型关系图, 看完后你会为它标准库里的类型个数吓倒。
为了让讨论简单起见, 暂时忽略中间CLOS的standard-object相关的部分(CLOS可以视为相对独立的部分),
CL标准中定义的基本类型包括了,
- 字符character, 分为支持基本字符集的base-char(ascii)和扩展字符集的extended-char(unicode)
- 函数function, 如果是编译型的,那就等于compiled-function
- 数组array, 一维的vector和多维的simple-array
- 序列sequence, 数组vector和列表list, list又有cons和nil
- 字串string是包含character的vector
其余还有众多的错误和警告的condition(类似于OOP中的exception的地位), 包package, 哈希表hash-table, reader的read-table等等
从这个类型关系里我们可以发现几个特点,
- 有些子类型同时从属于两种父类型,即类型有交集的部分,特别是序列sequence,符号symbol和数组array。
- 集和类型因为其包含的元素类型不同也有细分,如vector细分为string, bit-vector
SBCL中类型的底层表示
这里要讨论的是SBCL中关于上述的值类型在runtime里是以什么形式表示的。 (主要的参考资料是CMUCL desgin)
由于是动态类型, 类型是在运行期绑定在值(对象)上的, 因此, 所谓的类型的表示形式,其实就是各种值(对象)在runtime里的表示形式。
这里仅从32位 80x86系统的SBCL运行时的现状出发分析值及其类型的表示形式。
为了搞清楚底层表示,首先了解一下术语,
首先, SBCL中的值是以32位(8字节)为存储单元(最小存储单位)进行存储的, 32位的数据长度在SBCL中被称作word。
其次,一般值的第一个word的低3位,被用作表示该值所属的大类型的划分, 这3位被称作low-tag,
(仅仅靠low-tag的3位是无法区分所有值的类型的, 而且除去low-tag的word的其余28位也无法放下大部分类型的值。 )
需要占用一定连续的存储单元(在堆上的连续word)的值,占用的连续区域被称作data-block。
这样的值通常是由一个存储了指向data-block的指针的一个word和存储了实际值的data-block(至少两个word以上)构成,
data-block的第一个word的低8位一般用于表示该值所属的小类型, 这8位被称作wide-tag(其中的低3位仍然是low-tag)。
SBCL中关于类型的信息主要存放在low-tag和wide-tag里, 因此,通过掌握SBCL中预先定义的wide-tag和low-tag,
我们就能一窥关于值的类型的表示。 通过apropos, 我们可以看到wide-tag和low-tag主要定义在sb-vm的包中(early-objdef.lisp文件),
大致有以下这些:
low-tag
- ODD-FIXNUM-LOWTAG : 100
- EVEN-FIXNUM-LOWTAG : 000
- FUN-POINTER-LOWTAG : 101
- INSTANCE-POINTER-LOWTAG : 001
- LIST-POINTER-LOWTAG : 011
- OTHER-POINTER-LOWTAG : 111 // not function, clos instance, or cons
- OTHER-IMMEDIATE-0-LOWTAG : 010 // character
- OTHER-IMMEDIATE-1-LOWTAG : 110
wide-tag
- BIGNUM-WIDETAG : 00001010
- RATIO-WIDETAG : 00001110
- DOUBLE-FLOAT-WIDETAG : 00010110
- SINGLE-FLOAT-WIDETAG : 00010010
- COMPLEX-WIDETAG : 00011010
- COMPLEX-DOUBLE-FLOAT-WIDETAG : 00100010
- COMPLEX-SINGLE-FLOAT-WIDETAG : 00011110
- CHARACTER-WIDETAG : 01000010
- SYMBOL-HEADER-WIDETAG : 00111110
- COMPLEX-ARRAY-WIDETAG : 11111010
- COMPLEX-VECTOR-WIDETAG : 11110110
- COMPLEX-VECTOR-NIL-WIDETAG : 11101110 // empty string
- COMPLEX-BASE-STRING-WIDETAG : 11101010 // non-empty string
- COMPLEX-CHARACTER-STRING-WIDETAG : 11100110
- COMPLEX-BIT-VECTOR-WIDETAG : 11110010
- SIMPLE-ARRAY-COMPLEX-DOUBLE-FLOAT-WIDETAG : 11001110
- SIMPLE-ARRAY-COMPLEX-SINGLE-FLOAT-WIDETAG : 11001010
- SIMPLE-ARRAY-DOUBLE-FLOAT-WIDETAG : 11000110
- SIMPLE-ARRAY-SINGLE-FLOAT-WIDETAG : 11000010
- SIMPLE-ARRAY-FIXNUM-WIDETAG : 10111010
- SIMPLE-ARRAY-UNSIGNED-FIXNUM-WIDETAG : 10100110
- SIMPLE-ARRAY-SIGNED-BYTE-16-WIDETAG : 10110110
- SIMPLE-ARRAY-SIGNED-BYTE-32-WIDETAG : 10111110
- SIMPLE-ARRAY-SIGNED-BYTE-8-WIDETAG : 10110010
- SIMPLE-ARRAY-UNSIGNED-BYTE-15-WIDETAG : 10011110
- SIMPLE-ARRAY-UNSIGNED-BYTE-16-WIDETAG : 10100010
- SIMPLE-ARRAY-UNSIGNED-BYTE-2-WIDETAG : 10001110
- SIMPLE-ARRAY-UNSIGNED-BYTE-31-WIDETAG : 10101010
- SIMPLE-ARRAY-UNSIGNED-BYTE-32-WIDETAG : 10101110
- SIMPLE-ARRAY-UNSIGNED-BYTE-4-WIDETAG : 10010010
- SIMPLE-ARRAY-UNSIGNED-BYTE-7-WIDETAG : 10010110
- SIMPLE-ARRAY-UNSIGNED-BYTE-8-WIDETAG : 10011010
- SIMPLE-ARRAY-WIDETAG : 10001010
- SIMPLE-ARRAY-NIL-WIDETAG : 11011010 // empty string
- SIMPLE-BASE-STRING-WIDETAG : 11011110 // non-empty string
- SIMPLE-CHARACTER-STRING-WIDETAG : 11100010
- SIMPLE-BIT-VECTOR-WIDETAG : 11010010
- SIMPLE-VECTOR-WIDETAG : 11010110
- SAP-WIDETAG : 01000110 // system area pointer (an abstraction of c pointer like object)
- CODE-HEADER-WIDETAG : 00100110
- RETURN-PC-HEADER-WIDETAG : 00110110
- SIMPLE-FUN-HEADER-WIDETAG : 00101010
- CLOSURE-HEADER-WIDETAG : 00101110
- FUNCALLABLE-INSTANCE-HEADER-WIDETAG : 00110010
- FDEFN-WIDETAG : 01010110
- INSTANCE-HEADER-WIDETAG : 01010010
- NO-TLS-VALUE-MARKER-WIDETAG : 01011010 // thread related
- UNBOUND-MARKER-WIDETAG : 01001010 // clos slot-value unbound related
- VALUE-CELL-HEADER-WIDETAG : 00111010
- WEAK-POINTER-WIDETAG : 01001110
另外,在sb-kernel包(kernel.lisp)中有lowtag-of, widetag-of或%other-pointer-widetag可以获得参数值的lowtag和widetag。
(未完待续。。。)