Это ответ на вопрос, почему "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
Эта часть определяет две локальные (с точки зрения кода сборки) «переменные» (фактически метки), L_OBJC_METH_VAR_NAME_4
и L_OBJC_SELECTOR_REFERENCES_5
. Текст .objc_meth_var_names
и .objc_message_refs
, непосредственно перед метками 'variable', говорит ассемблеру, в какой раздел объектного файла следует поместить «материал, который следует». Разделы имеют значение для компоновщика. 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()
.
- Вам необходимо динамически изменять селектор во время выполнения.
Все селекторы должны пройти через sel_registerName()
, который регистрирует каждый SEL
ровно один раз. Это имеет то преимущество, что для любого заданного селектора используется везде одно значение. Хотя частная деталь реализации SEL
"обычно" является просто char *
указателем на копию селектора C
строкового текста.
Теперь вы знаете. И знание - это полдела!