HMDT - Logic and Intuition -

about HMDT

CoreFoundation の秘密

- String Servcies -

定数文字列のつくり方

CFString での文字列の作り方は、大きく分けて 3 つの方法があるんだ。

という 3 つ。ちなみに、インライン文字列であるかどうかということと、固定文字列であるかどうかということは、必ずしも一致しないので注意。固定文字列だけどインラインじゃない、つまり大きくてインラインにはならない、ということもあるのだ。

ここでは、定数文字列の作り方を見ていくぜ。

定数文字列の作り方

定数文字列を作るには CFSTR() マクロを使う。使い方はこんな感じ。

(sample)

CFStringRef hello = CFSTR("Hello, world.");

という感じで、C の文字列から CFString を作り出すんだ。ここで気をつけなきゃいけないのは、CFSTR() は新しい CFString のインスタンスを作り出すものではない、ということだ。だから、ここで得られた参照を release してはいけない。Apple の説明によると、「これは C で "hello world" ってプログラムの中に書いたようなものなんだ。ほら、こういうときは free しないでしょ?それと似たようなもので、CFSTR() で得られた文字列は release してはいけないんだ」

いささか無理があるような気もする説明だ。で、実際のところはどうなっているかというと、CFSTR() で作られた文字列はキャッシュされてるんだ。最初の 1 個は普通に作られて、キャッシュされる。次に同じものが要求されたときは、キャッシュされているインスタンスを返す。だから、次のソースコードの 2 つの関数は、メモリの使用量という観点からは、同じパフォーマンスのはずだ。

(sample)

void sample0() {
    CFStringRef tmp;
    tmp = CFSTR("Sample string");
    func0(tmp);
    func1(tmp);
}

void sample1() {
    func0(CFSTR("Sample string"));
    func1(CFSTR("Sample string"));
}

一度作られた定数文字列はキャッシュされるから、インスタンスが複数生成されるわけではないんだ。

CFSTR() に関する他の注意点は、こいつは仕様では 7 bit 文字しか受け付けないこと。一応、Mac OS Roman の文字は対応するように実装されているようだけど、将来どうなるかはわからんので使わない方が無難。とうぜん、是非は別にして、日本語も不可。

キャッシュ用のテーブル

文字列をキャッシュしておくテーブルには CFMutableDictionary を使っている。constantStringTable っていう変数だ。

CoreFoundation/String.subproj/CFString.c

static CFMutableDictionaryRef constantStringTable = NULL;

こいつのインスタンスは、CFSTR() で呼ばれる __CFStringMakeConstantString() が初めて呼ばれたときに作られる。ちなみに、容量の初期値は 2500。

CFSTR() の実装

では本題、実装を見ていこう。CFSTR() マクロの定義はこう。

CoreFoundation/String.subproj/CFString.h

CoreFoundation/Strign.subproj/CFString.h
#ifdef __CONSTANT_CFSTRINGS__
#define CFSTR(cStr)  ((CFStringRef) __builtin___CFStringMakeConstantString ("" cStr ""))
#else
#define CFSTR(cStr)  __CFStringMakeConstantString("" cStr "")
#endif

というわけで、__CFStringMakeConstantString() が呼び出される。これは定数文字列を作るための関数。この中での処理の流れは次のような感じだ。

  • キャッシュテーブルがあるかどうかチェック。無いなら作る
  • キャッシュテーブルの中に対応する値を取り出す
  • 対応する値が無いなら文字列を作って、テーブルに入れる
  • 値を返す

これを頭において、実際のコードを抜き出してみたよ。

CoreFoundation/String.subproj/CFString.c

CFStringRef __CFStringMakeConstantString(const char *cStr) {
    CFStringRef result;
    /* キャッシュテーブルがあるかどうかチェック */
    if (constantStringTable == NULL) {
        /* キャッシュテーブルが無かった場合 */
        CFDictionaryKeyCallBacks constantStringCallBacks = 
            {0, NULL, NULL, __cStrCopyDescription, __cStrEqual, __cStrHash};
        /* キャッシュテーブルをつくる */
        constantStringTable = CFDictionaryCreateMutable(
            NULL, 0, &constantStringCallBacks, &kCFTypeDictionaryValueCallBacks);
        _CFDictionarySetCapacity(constantStringTable, 2500); // avoid lots of rehashing
        ...
    }

    __CFSpinLock(&_CFSTRLock);
    /* キャッシュテーブルから文字列を取り出す */
    if (result = (CFStringRef)CFDictionaryGetValue(constantStringTable, cStr)) {
        /* 文字列があった場合 */
        __CFSpinUnlock(&_CFSTRLock);
    } else {
        /* 文字列が無かった場合 */
        __CFSpinUnlock(&_CFSTRLock);

        /* ASCII かどうかチェック */
        ...

        if (!isASCII) {
            /* ASCII じゃなかったときの処理 */
        }
        /* 文字列をつくる */
        result = CFStringCreateWithCString(
            constantStringAllocatorForDebugging, cStr, kCFStringEncodingMacRoman);
        /* エラーチェックなど */
        ...

        {
            ...
            /* キャッシュテーブルに文字列を入れる */
            CFIndex count;
            __CFSpinLock(&_CFSTRLock);
            count = CFDictionaryGetCount(constantStringTable);
            CFDictionaryAddValue(constantStringTable, key, result);
            if (CFDictionaryGetCount(constantStringTable) == count) {
                // add did nothing, someone already put it there
                result = (CFStringRef)CFDictionaryGetValue(
                                constantStringTable, key);
            }
            __CFSpinUnlock(&_CFSTRLock);
        ...
        }
    }

    return result;
}

大きな流れはこんな感じ。キャッシュテーブルの処理がメインだ。ASCII じゃないときのエラー処理もあるけど、ここでは省いた。

実際に文字列を作ることになるのは、CFStringCreateWithCString() 関数だ。これについては、次の項で見るよ。

back to top content

Copyright © 2002-2007 HMDT. All rights reserved.