この答えは理由"initializer element is not constant"
です。
次の例を考えます。
SEL theSelector; // Global variable
void func(void) {
theSelector = @selector(constantSelector:test:);
}
i386
アーキテクチャ用に次のようにコンパイルします。
.objc_meth_var_names
L_OBJC_METH_VAR_NAME_4:
.ascii "constantSelector:test:\0"
.objc_message_refs
.align 2
L_OBJC_SELECTOR_REFERENCES_5:
.long L_OBJC_METH_VAR_NAME_4
この部分では、2 つのローカル (アセンブリ コードに関して) '変数' (実際にはラベル)L_OBJC_METH_VAR_NAME_4
とL_OBJC_SELECTOR_REFERENCES_5
. 'variable' ラベルの直前のテキスト.objc_meth_var_names
andは、オブジェクト ファイルのどのセクションに「続くもの」を配置するかをアセンブラに指示します。.objc_message_refs
セクションはリンカーにとって意味があります。 L_OBJC_SELECTOR_REFERENCES_5
のアドレスに初期設定されていますL_OBJC_METH_VAR_NAME_4
。
実行ロード時、プログラムが実行を開始する前に、リンカは次のような処理を行います。
- セクション内の各エントリを反復処理します
.objc_message_refs
。
- 各エントリは、最初は
0
終了C
文字列へのポインタに設定されます。
- この例では、ポインターは最初に、文字列
L_OBJC_METH_VAR_NAME_4
を含む のアドレスに設定されます。ASCII
C
"constantSelector:test:"
- 次に実行
sel_registerName("constantSelector:test:")
し、戻り値を に格納し
L_OBJC_SELECTOR_REFERENCES_5
ます。プライベートな実装の詳細を知っているリンカは、sel_registerName()
文字どおりに呼び出すことはできません。
基本的に、リンカーはこの例のロード時にこれを実行します。
L_OBJC_SELECTOR_REFERENCES_5 = sel_registerName("constantSelector:test:");
"initializer element is not constant"
これが、初期化要素がコンパイル時に定数でなければならない理由です。プログラムが実行を開始するまで、値は実際にはわかりません。その場合でも、struct
宣言は別のリンカ セクションである.data
セクションに格納されます。SEL
リンカーは、セクション内の値を更新する方法しか認識しておらず、その実行時に計算された値を から任意の場所に.objc_message_refs
「コピー」する方法はありません。SEL
.objc_message_refs
.data
C
ソースコードは...
theSelector = @selector(constantSelector:test:);
... になります:
movl L_OBJC_SELECTOR_REFERENCES_5, %edx // The SEL value the linker placed there.
movl L_theSelector$non_lazy_ptr, %eax // The address of theSelector.
movl %edx, (%eax) // theSelector = L_OBJC_SELECTOR_REFERENCES_5;
リンカはプログラムが実行される前にすべての作業L_OBJC_SELECTOR_REFERENCES_5
を行うため、 を呼び出した場合に得られる値とまったく同じ値が含まれていますsel_registerName("constantSelector:test:")
。
theSelector = sel_registerName("constantSelector:test:");
違いは、これは関数呼び出しであり、セレクターが既に登録されている場合は関数がセレクターを見つける実際の作業を行うか、SEL
セレクターを登録するために新しい値を割り当てるプロセスを実行する必要があることです。これは、定数値をロードするだけの場合よりもかなり遅くなります。これは「遅く」なりますが、任意のC
文字列を渡すことができます。これは、次の場合に役立ちます。
- セレクターはコンパイル時には認識されません。
- 直前に
sel_registerName()
呼び出されるまで、セレクターはわかりません。
- 実行時にセレクターを動的に変更する必要があります。
すべてのセレクターは、それぞれを正確に 1 回sel_registerName()
登録する を通過する必要があります。SEL
これには、特定のセレクターに対して、どこでも 1 つの値しかないという利点があります。実装のプライベートな詳細ですSEL
が、「通常」char *
は、セレクターC
文字列テキストのコピーへのポインターにすぎません。
今、あなたは知っています。そして、知ることは戦いの半分です!