home link download back number special issue

HMDT - Special Issue / CoreFoundation の秘密 / String Services - __CFString の構造


- String Servcies -
__CFString の構造

ここでは、CFStringRef と CFMutableStringRef の実体である __CFString 構造体を見ていくぜ!文字列の属性情報と、文字列データの格納の仕方だ。

__CFString 構造体

構造体の定義は、ずばり次の通りだ。

CoreFoundation/String.subproj/CFBase.h
struct __CFString {
    CFRuntimeBase base;
    union { // In many cases the allocated structs are smaller than these
    struct {
        SInt32 length;
    } inline1;
    struct {
        void *buffer;
        UInt32 length;
        CFAllocatorRef contentsDeallocator; // Just the dealloc func is used
    } externalImmutable1;
    struct {
        void *buffer;
        CFAllocatorRef contentsDeallocator; // Just the dealloc func is used
    } externalImmutable2;
    struct {
        void *buffer;
        UInt32 length;
        UInt32 capacityFields; // Currently only stores capacity
        UInt32 gapEtc; // Stores some bits, plus desired or fixed capacity
        CFAllocatorRef contentsAllocator; // Optional
    } externalMutable;
    } variants;
};

先頭には、CoreFoundation オブジェクトのお約束で CFRuntimeBase がある。その次は union の変数 variants がある。variants は状況に応じて次の 4 つのどれかになるぜ。

inline1
文字列が比較的小さい場合は、この構造体の後に直接文字列データが来る。この場合、variants は inline1 を使って、これをインライン文字列って呼ぶんだ。

externalImmutable1
これは文字列のデータがこの構造体の外にあって、固定長の場合。buffer がデータのポインタで、length が文字列の長さだ。

externalImmutable2
こっちは文字列のデータが外にあって、固定長っていうところはいっしょなんだけど、文字列の長さがデータに埋め込まれている場合。つまりは pascal 文字列だ。文字列の先頭バイトが文字列の長さを表す。

externalMutable
これは文字列のデータが外にあって、可変長の場合。buffer がデータのポインタで、length が文字列の長さ。

というわけで、この 4 つを使い分けることになる。

文字列の属性

__CFString には、上で見ような union があるんだけど、どの構造体を使っているか、っていう情報はどこにあるのか?実は、CFRuntimeBase の __info フィールドを使っている。そうきたかい。ちなみに、Base Service による __info フィールどの使われ方はこちらを参照。CFString では、下位 8 ビットを次のように使っているんだ。

I: 固定長かどうか
E: インラインかどうか
U: Unicode かどうか
N: NULL を使っているかどうか
L: length があるかどうか
D: deallocator が指定されているかどうか
X: 外部にデータがある可変長か

6 ビット目と 7 ビット目はちょっと変則的。

で、こいつらに対するマクロが以下のように用意されてる。

CoreFoundation/String.subproj/CFBase.h
CF_INLINE Boolean __CFStrIsMutable(CFStringRef str)
CF_INLINE Boolean __CFStrIsExternalMutable(CFStringRef str)
CF_INLINE Boolean __CFStrIsInline(CFStringRef str)
CF_INLINE Boolean __CFStrFreeContentsWhenDone(CFStringRef str)
CF_INLINE Boolean __CFStrHasContentsDeallocator(CFStringRef str)
CF_INLINE Boolean __CFStrIsUnicode(CFStringRef str)
CF_INLINE Boolean __CFStrIsEightBit(CFStringRef str)
CF_INLINE Boolean __CFStrHasNullByte(CFStringRef str)
CF_INLINE Boolean __CFStrHasLengthByte(CFStringRef str)
CF_INLINE Boolean __CFStrHasExplicitLength(CFStringRef str)

というわけで、__info ビットとそれに対するマクロで文字列の属性を知ることができるんだ。

文字列データ

次の問題。文字列データはどこに格納されるのか?文字列データを取り出すには __CFStrContents() を使う。

CoreFoundation/String.subproj/CFBase.h
CF_INLINE const void *__CFStrContents(CFStringRef str) {
    if (__CFStrIsInline(str)) {
        return (const void *)(((UInt32)&(str->variants)) + 
                (__CFStrHasExplicitLength(str) ? sizeof(UInt32) : 0));
    } else { // External; pointer is always word 2
        return str->variants.externalImmutable1.buffer;
    }
}

これによると、インライン文字列で length が指定されるとき、されないとき、インライン文字列じゃないとき、っていう 3 つの場合があることが分かる。それぞれ次のような感じになる。図は、__CFStringvariants から下を示しているよ。

インライン文字列で length が指定されてるとき

先頭 4 バイトが length で 5 バイト目から文字列データ。

インライン文字列で length が指定されていないとき

これはおそらく pascal 文字列用。最初から文字列データ。先頭バイトには文字列の長さが埋め込まれている。

外部にデータがあるとき

インライン以外の文字列はこれ。先頭 4 バイトが length。次の 4 バイトが buffer へのポインタ。ポインタの先に文字列データがある。

余談だけど、インライン文字列のとき、UInt32 でキャストしてるけど、これ UInt32* でキャストするべきじゃないのか?ま、どっちでも動くからいいけど。

(Nov. 13, 2002)


Home | Link | Download | Back Number | Speciall Issue

mailto: mkino@xd5.so-net.ne.jp

HMDT