nil / NULL 블록이 실행될 때 버스 오류를 일으키는 이유는 무엇입니까?
나는 블록을 많이 사용하기 시작했고 곧 nil 블록이 버스 오류를 일으킨다는 것을 알게되었습니다.
typedef void (^SimpleBlock)(void);
SimpleBlock aBlock = nil;
aBlock(); // bus error
이것은 nil 객체에 대한 메시지를 무시하는 Objective-C의 일반적인 동작에 위배되는 것 같습니다.
NSArray *foo = nil;
NSLog(@"%i", [foo count]); // runs fine
따라서 블록을 사용하기 전에 일반적인 nil 검사에 의존해야합니다.
if (aBlock != nil)
aBlock();
또는 더미 블록을 사용하십시오.
aBlock = ^{};
aBlock(); // runs fine
다른 옵션이 있습니까? nil 블록이 단순히 nop가 될 수없는 이유가 있습니까?
좀 더 완전한 답변으로 이것을 좀 더 설명하고 싶습니다. 먼저이 코드를 살펴 보겠습니다.
#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가, 16 진수로 C가 있음을 알 수 있습니다.
따라서 블록이 호출되면 프로세서는 블록의 주소를 가져 와서 12를 더한 다음 해당 메모리 주소에있는 값을로드하려고합니다. 그런 다음 해당 주소로 점프하려고합니다. 그러나 블록이 nil이면 주소 읽기를 시도합니다 0xc
. 이것은 분명히 더프 주소이므로 세분화 오류가 발생합니다.
이제 Objective-C 메시지 호출처럼 조용히 실패하는 것이 아니라 이와 같은 충돌이어야하는 이유는 실제로 설계 선택입니다. 컴파일러가 블록을 호출하는 방법을 결정하는 작업을 수행하기 때문에 블록이 호출되는 모든 곳에 nil 검사 코드를 삽입해야합니다. 이로 인해 코드 크기가 증가하고 성능이 저하됩니다. 또 다른 옵션은 nil 검사를 수행하는 트램폴린을 사용하는 것입니다. 그러나 이것은 또한 성능 저하를 초래합니다. Objective-C 메시지는 실제로 호출 될 메서드를 찾아야하기 때문에 이미 트램펄린을 통과합니다. 런타임은 메소드의 지연 주입 및 메소드 구현 변경을 허용하므로 어쨌든 이미 트램폴린을 통과하고 있습니다. 이 경우 nil 검사를 수행하는 추가 패널티는 중요하지 않습니다.
근거를 설명하는 데 도움이되기를 바랍니다.
Matt Galloway의 대답은 완벽합니다! 좋은 읽기!
삶을 더 편하게하는 몇 가지 방법이 있다는 것을 덧붙이고 싶습니다. 다음과 같이 매크로를 정의 할 수 있습니다.
#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;
이렇게하면 대체 값을 쉽게 정의 할 수 있습니다. 내 예에서 'nil'.
주의 사항 : 저는 Blocks 전문가가 아닙니다.
Blocks are objective-c objects but calling a block is not a message, although you could still try [block retain]
ing a nil
block or other messages.
Hopefully, that (and the links) helps.
This is my simple nicest solution… Maybe there is possible to write one universal run function with those c var-args but I don’t know how to write that.
void run(void (^block)()) {
if (block)block();
}
void runWith(void (^block)(id), id value) {
if (block)block(value);
}
참고URL : https://stackoverflow.com/questions/4145164/why-do-nil-null-blocks-cause-bus-errors-when-run
'Development Tip' 카테고리의 다른 글
Node.js : ImageMagick없이 이미지 크기 조정 (0) | 2020.10.25 |
---|---|
호스트 외부 (동일 네트워크)에서 도커 컨테이너에 연결하는 방법 [Windows] (0) | 2020.10.25 |
Wikipedia 페이지 ID는 무엇입니까? (0) | 2020.10.25 |
DateTime을 24 시간 형식으로 지정하는 방법은 무엇입니까? (0) | 2020.10.25 |
Node.js 'types'는 .ts 파일에서만 사용할 수 있습니다.-Visual Studio Code using @ ts-check (0) | 2020.10.25 |