Я хотел бы объяснить это немного подробнее с более полным ответом.Сначала давайте рассмотрим этот код:
#import <Foundation/Foundation.h>
int main(int argc, char *argv[]) {
void (^block)() = nil;
block();
}
Если вы запустите это, вы увидите сбой в строке block()
, который выглядит примерно так (при запуске на 32-битной архитектуре - это важно):
EXC_BAD_ACCESS (код = 2, адрес = 0xc)
Итак, почему это так?Ну, 0xc
это самый важный бит.Сбой означает, что процессор попытался прочитать информацию по адресу памяти 0xc
.Это почти совершенно неверная вещь.Вряд ли там что-то есть.Но почему он попытался прочитать это место в памяти?Что ж, это связано с тем, как блок на самом деле создается под капотом.
Когда блок определен, компилятор фактически создает структуру в стеке, такую форму:
struct Block_layout {
void *isa;
int flags;
int reserved;
void (*invoke)(void *, ...);
struct Block_descriptor *descriptor;
/* Imported variables. */
};
Блок является указателем на эту структуру.Четвертый член invoke
этой структуры интересен.Это указатель на функцию, указывающий на код, в котором хранится реализация блока.Таким образом, процессор пытается перейти к этому коду при вызове блока.Обратите внимание, что если вы подсчитаете количество байтов в структуре перед элементом invoke
, вы обнаружите, что их число равно десятичному числу 12 или шестнадцатеричному числу C.
Таким образом, когда вызывается блок, процессорберет адрес блока, добавляет 12 и пытается загрузить значение, хранящееся в этом адресе памяти.Затем он пытается перейти по этому адресу.Но если блок равен нулю, он попытается прочитать адрес 0xc
.Ясно, что это ненадежный адрес, и поэтому мы получаем ошибку сегментации.
Теперь причина, по которой это должно произойти сбой, подобный этому, а не молчаливый сбой, как при вызове сообщения Objective C, на самом деле является выбором дизайна.Поскольку компилятор решает, как вызывать блок, он должен вводить нулевой проверочный код везде, где вызывается блок.Это увеличит размер кода и приведет к снижению производительности.Другой вариант - использовать батут, который проверяет ноль.Однако это также повлечет за собой снижение производительности.Сообщения Objective-C уже проходят через батут, так как им нужно найти метод, который на самом деле будет вызван.Среда выполнения позволяет лениво внедрять методы и изменять реализации методов, так что в любом случае он уже проходит через батут.Дополнительное наказание за выполнение нулевой проверки в этом случае не имеет значения.
Я надеюсь, что это немного поможет объяснить обоснование.
Для получения дополнительной информации см. Мой блог сообщений .