Ответ 1
Я хотел бы объяснить это немного больше, с более полным ответом. Сначала рассмотрим этот код:
#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
. Это, безусловно, адрес duff, и мы получаем ошибку сегментации.
Теперь причина, по которой это может быть такая авария, а не просто неудачная, как вызов сообщения Objective-C, действительно является выбором дизайна. Поскольку компилятор выполняет работу по решению о вызове блока, ему придется вводить код проверки nil везде, где вызывается блок. Это увеличит размер кода и приведет к плохой производительности. Другой вариант - использовать батут, который проверяет нуль. Однако это также повлечет за собой штраф за исполнение. Objective-C сообщения уже проходят через батут, поскольку они должны искать метод, который будет фактически вызван. Среда исполнения позволяет лениво вводить методы и изменять реализации методов, поэтому она все равно проходит через батут. Дополнительный штраф за выполнение проверки нулевого значения в этом случае невелик.
Надеюсь, это поможет немного объяснить обоснование.
Для получения дополнительной информации см. блог .