Почему блоки nil / NULL вызывают ошибки шины при запуске?
Я начал использовать блоки много и вскоре заметил, что нулевые блоки вызывают ошибки шины:
typedef void (^SimpleBlock)(void);
SimpleBlock aBlock = nil;
aBlock(); // bus error
это, кажется, идет вразрез с обычным поведением Objective-C, который игнорирует сообщения для нулевых объектов:
NSArray *foo = nil;
NSLog(@"%i", [foo count]); // runs fine
поэтому я должен прибегнуть к обычной нулевой проверке, прежде чем использовать блок:
if (aBlock != nil)
aBlock();
или использовать фиктивные блоки:
aBlock = ^{};
aBlock(); // runs fine
есть другой вариант? Есть ли причина, почему nil blocks не может быть просто nop?
4 ответа:
Я хотел бы объяснить это немного больше, с более полный ответ. Сначала рассмотрим этот код:
#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, действительно является выбором дизайна. Поскольку компилятор выполняет работу по решению того, как вызвать блок, ему придется вводить код проверки nil везде, где вызывается блок. Этот увеличит размер кода и приведет к плохой производительности. Другой вариант - использовать батут, который делает нулевую проверку. Однако это также повлечет за собой штраф за производительность. Сообщения Objective-C уже проходят через батут, так как им нужно искать метод, который фактически будет вызван. Время выполнения позволяет лениво вводить методы и изменять реализации методов, поэтому он уже проходит через батут в любом случае. Дополнительное наказание за выполнение нулевой проверки не является значимый в данном случае.
Я надеюсь, что помогает немного пояснить.
ответ Мэтта Гэллоуэя идеален! Отличное чтение!
Я просто хочу добавить, что есть несколько способов, чтобы сделать жизнь проще. Вы можете определить макрос следующим образом:
#define BLOCK_SAFE_RUN(block, ...) block ? block(__VA_ARGS__) : nil
он может принимать 0 – n аргументов. Пример использования
typedef void (^SimpleBlock)(void); SimpleBlock simpleNilBlock = nil; SimpleBlock simpleLogBlock = ^{ NSLog(@"working"); }; BLOCK_SAFE_RUN(simpleNilBlock); BLOCK_SAFE_RUN(simpleLogBlock); typedef void (^BlockWithArguments)(BOOL arg1, NSString *arg2); BlockWithArguments argumentsNilBlock = nil; BlockWithArguments argumentsLogBlock = ^(BOOL arg1, NSString *arg2) { NSLog(@"%@", arg2); }; BLOCK_SAFE_RUN(argumentsNilBlock, YES, @"ok"); BLOCK_SAFE_RUN(argumentsLogBlock, YES, @"ok");
если вы хотите получить возвращаемое значение блока и вы не уверены, если блок существует или нет, то вы, вероятно, лучше просто наберите:
block ? block() : nil;
таким образом, вы можете легко определить резервное значение. В моем примере "ноль".
предостережение: я не эксперт в блоках.
блоки areobjective-C objects но вызов a блок не ответ, хотя вы все еще можете попробовать
[block retain]
выборnil
блокировать или другие сообщения.надеюсь, что (и ссылки) помогает.