読者です 読者をやめる 読者になる 読者になる

メモリ内容出力+stringのメモリ表現

sbcl FFI

SBCLでマシンコードを直接実行で作成したstring-to-alien-functionマクロを使って少し遊んでみる。
SBCL: sbcl-1.0.28-x86-linux-binary.tar.bz2


まずは、指定されたメモリの内容を出力する関数を定義。
※ もともとは、文字列を出力する関数のつもりだったが、機能的には上の方が適切なので変更。

; 元となるマシンコード
; 左から順に、オフセット、機械語、アセンブリ言語
;0000 55        pushl   %ebp
;0001 89E5      movl    %esp, %ebp
;0003 8B4D08    movl    8(%ebp), %ecx
;0006 53        pushl   %ebx
;0007 8B550C    movl    12(%ebp), %edx
;000a B8040000  movl    $4,%eax # 4=出力用のシステムコール(?)
;     00
;000f BB010000  movl    $1,%ebx # 1=標準出力
;     00
;0014 CD80      int     $0x80
;0016 5B        popl    %ebx
;0017 5D        popl    %ebp
;0018 C3        ret

;; 関数定義
(let* ((code "5589E58B4D08538B550CB804000000BB01000000CD805B5DC3")
       (fn (string-to-alien-function code (function int int int)))) ;(function 返り値 アドレス 表示サイズ)
  (defun print-memory (#1=address-or-object size &optional (offset 0))
    (unless (typep #1# 'fixnum)  ; fixnumならメモリアドレスと仮定
      (setf #1# (sb-kernel:get-lisp-obj-address #1#)))
    (sb-alien:alien-funcall fn (+ #1# offset) size)))


この関数に文字列(simple-string)を渡して、それがメモリ上でどのように表現されているのかを見てみる。
※ 下の出力はemacsによるもの。^@は、Cで云うNULL文字、lispなら(code-char 0)の文字に該当する。

> (print-memory "simple-string" 55)
出力: ^@s^@^@^@i^@^@^@m^@^@^@p^@^@^@l^@^@^@e^@^@^@-^@^@^@s^@^@^@t^@^@^@r^@^@^@i^@^@^@n^@^@^@g^@^@^@^@^@

;; 少し変えてみる
> (print-memory (string-downcase "SIMPLE-STRING") 55)
;; 結果は同じ
出力: ^@s^@^@^@i^@^@^@m^@^@^@p^@^@^@l^@^@^@e^@^@^@-^@^@^@s^@^@^@t^@^@^@r^@^@^@i^@^@^@n^@^@^@g^@^@^@^@^@

これを見ると、simple-stringは、1文字ごとに4byte割り当てているのが分かる。
sbclのchar-code-limitは#x11000なので、(各4byteの)2byte目以降の3つは文字の表現用に割り当てているのだろうが、先頭の1byteを何に使っているのかは不明。(cf. (print-memory (coerce "simple-string" 'simple-vector) 55))
それにしても、一つの文字に一律に4byteを割り当てるのは、少しもったいないような気がする。


また下のように、文字列の長さが文字列のアドレスの直前に格納されていた。

;; 4byte分、前のアドレスも表示
> (print-memory "simple-string" 55 -4)
出力: ^@4^@^@^@s^@^@^@i^@^@^@m^@^@^@p^@^@^@l^@^@^@e^@^@^@-^@^@^@s^@^@^@t^@^@^@r^@^@^@i^@^@^@n^@^@^@g^@^@^@^@^@
;; (/ (char-code #\4) 4) == (length "simple-string") == 13


そして何故か、adjustableやfill-pointerが設定されている場合は、文字列のアドレスよりも前に文字列の中身が格納されていた。

>(defparameter *str* (make-array 13 :fill-pointer 0 :element-type 'character))

>(dotimes (i 13)
   (vector-push (aref "simple-string" i) *str*))

> (print-memory *str* 64 -64)
;; 「^@4^@^@」の位置は変わらないが、「^@s^@^@^@i^@^@ ...」の位置が前方に移動している
出力: ^@s^@^@^@i^@^@^@m^@^@^@p^@^@^@l^@^@^@e^@^@^@-^@^@^@s^@^@^@t^@^@^@r^@^@^@i^@^@^@n^@^@^@g^@^@^@^@^@^@^@\356^G^@^@4^@^@


symbolの場合は(も?)少し特殊で、(おそらく)組み込みのsymbolかユーザがinternしたsymbolかでメモリへの配置の仕方が変わっている。

;; 組み込みの場合
> (print-memory (symbol-name 'simple-string) 20 -4)
;; 1文字1byteで表現されている
出力: ^@4^@^@^@SIMPLE-STRING^@^@

;; ユーザが(暗黙に)internした場合
> (print-memory (symbol-name 'my-simple-string) 68 -4)
;; 通常のsimple-stringと同じように、1文字に4byteが使われている
出力: ^@@^@^@^@M^@^@^@Y^@^@^@-^@^@^@S^@^@^@I^@^@^@M^@^@^@P^@^@^@L^@^@^@E^@^@^@-^@^@^@S^@^@^@T^@^@^@R^@^@^@I^@^@^@N^@^@^@G^@^@

以上。
その他に、構造体などでも試してみたが、文字列ほど直截的ではなかった。


最近は(私的には)lispばかり使っているので、こういったことを気にしなくなっていたが、久しぶりにメモリの中身とかを見てみると、やはりsbclで各種オブジェクトがどのように実装(メモリ表現)されているのかが、気になってくる。
すぐには無理だろうが、いつか時間のある時にでもソースコードをちゃんと調べてみたい。