Diese Antwort lautet: warum "initializer element is not constant"
.
Das folgende Beispiel:
SEL theSelector; // Global variable
void func(void) {
theSelector = @selector(constantSelector:test:);
}
Kompiliert zu etwas wie diesem für die i386
Architektur:
.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
In diesem Teil werden zwei lokale (im Sinne des Assembler-Codes) "Variablen" (eigentlich Bezeichnungen) definiert, L_OBJC_METH_VAR_NAME_4
y L_OBJC_SELECTOR_REFERENCES_5
. Der Text .objc_meth_var_names
y .objc_message_refs
kurz vor den "Variablen"-Labels, teilt dem Assembler mit, in welchen Abschnitt der Objektdatei "das Folgende" eingefügt werden soll. Die Abschnitte sind für den Linker von Bedeutung. L_OBJC_SELECTOR_REFERENCES_5
wird zunächst auf die Adresse von L_OBJC_METH_VAR_NAME_4
.
Zum Zeitpunkt des Ladens der Ausführung, bevor das Programm mit der Ausführung beginnt, macht der Linker ungefähr Folgendes:
- Iteriert über jeden Eintrag in der Datei
.objc_message_refs
Abschnitt.
- Jeder Eintrag wird zunächst auf einen Zeiger auf eine
0
gekündigt C
String.
- In unserem Beispiel wird der Zeiger zunächst auf den Adresse von
L_OBJC_METH_VAR_NAME_4
die enthält die ASCII
C
String "constantSelector:test:"
.
- Sie führt dann
sel_registerName("constantSelector:test:")
und speichert den zurückgegebenen Wert unter L_OBJC_SELECTOR_REFERENCES_5
. Der Linker, der private Implementierungsdetails kennt, darf nicht aufrufen sel_registerName()
buchstäblich.
In unserem Beispiel führt der Linker dies im Wesentlichen zur Ladezeit durch:
L_OBJC_SELECTOR_REFERENCES_5 = sel_registerName("constantSelector:test:");
Aus diesem Grund ist die "initializer element is not constant"
- das Initialisierungselement muss zur Kompilierzeit konstant sein. Der Wert ist erst dann bekannt, wenn das Programm ausgeführt wird. Selbst dann muss Ihr struct
Deklarationen in einem anderen Linker-Abschnitt gespeichert sind, wird die .data
Abschnitt. Der Linker weiß nur, wie er die SEL
Werte in der .objc_message_refs
Abschnitt, und es gibt keine Möglichkeit, diese zur Laufzeit berechnete SEL
Wert von .objc_message_refs
an eine beliebige Stelle in .data
.
Les C
Quellcode...
theSelector = @selector(constantSelector:test:);
... wird:
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;
Da der Linker seine gesamte Arbeit erledigt, bevor das Programm ausgeführt wird, L_OBJC_SELECTOR_REFERENCES_5
enthält genau denselben Wert, den Sie erhalten würden, wenn Sie sel_registerName("constantSelector:test:")
:
theSelector = sel_registerName("constantSelector:test:");
Der Unterschied besteht darin, dass es sich um einen Funktionsaufruf handelt und die Funktion die eigentliche Arbeit erledigen muss, um den Selektor zu finden, wenn er bereits registriert wurde, oder den Prozess der Zuweisung eines neuen SEL
Wert, um den Selektor zu registrieren. Dies ist wesentlich langsamer als das Laden eines konstanten Wertes. Obwohl dies 'langsamer' ist, erlaubt es Ihnen, eine beliebige C
String. Dies kann nützlich sein, wenn:
- Der Selektor ist zur Kompilierzeit nicht bekannt.
- Der Selektor ist erst kurz vor der
sel_registerName()
genannt wird.
- Sie müssen den Selektor während der Laufzeit dynamisch ändern.
Alle Selektoren müssen durch sel_registerName()
die jedes SEL
genau einmal. Dies hat den Vorteil, dass für jeden beliebigen Selektor überall genau ein Wert vorhanden ist. Dies ist jedoch ein privates Detail der Implementierung, SEL
ist "normalerweise" nur ein char *
Zeiger auf eine Kopie der Selektoren C
String-Text.
Jetzt wissen Sie es. Und Wissen ist die halbe Miete!