在 SBCL 中获取对象位长

因为字符串编码问题,引发笔者对SBCL的内存分配进行一次简单的探究。

起因

近期大量语言发生了突飞猛进的发展,很多古代语言从单纯的支持ASCII演变成支持utf8。 但是utf8并非是一个有效字符存储方案,所以在Erlang中一个中文字符会被定义为16位,也 就是我们常说的utf16,从而达到高效但非节约的存储。那么自己常用的Common Lisp中,一 个中文字符又是会被定义为多少位呢?

解决方案

首先要实现下面这函数

1
2
3
4
5
6
(defun get-object-size/octets (object)
  (sb-sys:without-gcing
    (nth-value 2 (sb-vm::reconstitute-object
                  (ash ;; 因为返回了一个fixnum对象的lispobj,因此需要进行这一步的处理
                        (logandc1 sb-vm:lowtag-mask (sb-kernel:get-lisp-obj-address object)) ;; 掩码掩掉最后nbit,得到lispobj真正地址,n取决于平台,x64是4
                       (- sb-vm:n-fixnum-tag-bits))))))

为什么会有这个函数呢,因为lisp在创建对象的时候和Erlang是一致的,都会将指针的空位 设置为标签掩码。

接着我们测试一个中文字符

1
2
(defvar *c* "中")
(get-object-size/octets *c*) ;; 32bit

但是查阅了SBCL的代码的时候,发现

1
2
(def!constant sb!xc:char-code-limit #!-sb-unicode 256 #!+sb-unicode #x110000
  "the upper exclusive bound on values produced by CHAR-CODE")

也就是说,SBCL会使用一个32bit的位长来表示一个utf16的字符,具体可以参阅Memory Layout

总结

从这个测试中可以发现,绝大部份古代语言真对宽字符会直接处理成utf16,而不是utf8。 当然也有一些特例,例如OCaml,一个字符依然是8bits,对宽字符会处理成utf8。