Block的内存结构
在苹果官方文档中,给出了block的结构体定义:
struct Block_literal_1 {
void *isa; // initialized to &_NSConcreteStackBlock or &_NSConcreteGlobalBlock
int flags;
int reserved;
void (*invoke)(void *, ...);
struct Block_descriptor_1 {
unsigned long int reserved; // NULL
unsigned long int size; // sizeof(struct Block_literal_1)
// optional helper functions
void (*copy_helper)(void *dst, void *src); // IFF (1<<25)
void (*dispose_helper)(void *src); // IFF (1<<25)
// required ABI.2010.3.16
const char *signature; // IFF (1<<30)
} *descriptor;
// imported variables
};
Block是如何捕获参数的:
OC代码如下:(为了代码的简洁,删掉了autoreleasepool相关代码)
int main(int argc, const char * argv[]) {
int variable = 0x12345678;
void (^block)(void) = ^{
printf("var = %x\n", variable);
};
block();
return 0;
}
可以使用下面的命令将OC代码转化为C++代码:
$ clang -rewrite-objc main.m -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int variable;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _variable, int flags=0) : variable(_variable) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int variable = __cself->variable; // bound by copy
printf("var = %x\n", variable);
}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
int variable = 0x12345678;
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, variable));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
return 0;
}
大多数人的误区
根据
int variable = __cself->variable
,variable是保存在栈区的。
其实非也。生成C++代码,是我们强制生成而已。正常编译的时候,OC是直接编译成机器指令的,并没有这一步骤。variable实际上是在block结构体所在的内存后面的。
编译后用Hopper查看:
_main:
0000000100000ef9 push rbp
0000000100000efa mov rbp, rsp
0000000100000efd push rbx
0000000100000efe sub rsp, 0x28
0000000100000f02 mov rax, qword [__NSConcreteStackBlock_100001010]
0000000100000f09 lea rdi, qword [rbp+var_30]
0000000100000f0d mov qword [rdi], rax ;isa = _NSConcreteStackBlock
0000000100000f10 mov eax, 0xc0000000
0000000100000f15 mov qword [rdi+8], rax ;flags = 0xc0000000
0000000100000f19 lea rax, qword [___main_block_invoke]
0000000100000f20 mov qword [rdi+0x10], rax ;invoke = ___main_block_invoke
0000000100000f24 lea rax, qword [___block_descriptor_tmp]
0000000100000f2b mov qword [rdi+0x18], rax ; descriptor = ___block_descriptor_tmp
0000000100000f2f mov dword [rdi+0x20], 0x12345678 ; argument "instance" for method imp___stubs__objc_retainBlock
0000000100000f36 call imp___stubs__objc_retainBlock
0000000100000f3b mov rbx, rax
0000000100000f3e mov rdi, rbx
0000000100000f41 call qword [rbx+0x10]
0000000100000f44 mov rdi, rbx ; argument "instance" for method _objc_release
0000000100000f47 call qword [_objc_release_100001018] ; _objc_release
0000000100000f4d xor eax, eax
0000000100000f4f add rsp, 0x28
0000000100000f53 pop rbx
0000000100000f54 pop rbp
0000000100000f55 ret
___main_block_invoke:
0000000100000f56 push rbp ; DATA XREF=_main+32
0000000100000f57 mov rbp, rsp
0000000100000f5a mov esi, dword [rdi+0x20] ; variable = dword [rdi+0x20]
0000000100000f5d lea rdi, qword [0x100000f9c] ; "var = %x\\n", argument "format" for method imp___stubs__printf
0000000100000f64 xor eax, eax
0000000100000f66 pop rbp
0000000100000f67 jmp imp___stubs__printf
通过汇编代码,可以知道block的创建流程是这样子的:
- 在栈上开辟了一块内存,用于存放Block的结构体;
- 将Block的isa赋值为_NSConcreteStackBlock;
- 由
mov dword [rdi+0x20], 0x12345678
可以看出,要传递的参数,是紧挨着Block_literal_1
结构保存的。解释了为什么在block外对参数操作,不会影响到block; - 赋值操作,ARC情况下,会给我们加上objc_retainBlock的操作;
- objc_retainBlock函数跟进去,是_Block_copy函数;
- _Block_copy再跟进去,看到malloc了一块内存,再通过memmove把传进来的block结构体复制到堆上,然后堆上的block的isa指针指向_NSConcreteMallocBlock;
- dword [rdi+0x20]这条指令取出block参数,可以看出参数并没有像C++代码那样赋值给一个栈变量。
__block
的底层实现
在原来的代码上,加上__block的编译指令:
int main(int argc, const char * argv[]) {
__block int variable = 0x12345678;
void (^block)(void) = ^{
printf("var = %x\n", variable);
};
block();
return 0;
}
_main:
0000000100000e17 push rbp
0000000100000e18 mov rbp, rsp
0000000100000e1b push rbx
0000000100000e1c sub rsp, 0x48
0000000100000e20 lea rax, qword [rbp+var_28]
0000000100000e24 mov qword [rax], 0x0
0000000100000e2b mov qword [rax+8], rax
0000000100000e2f movabs rcx, 0x2020000000
0000000100000e39 mov qword [rax+0x10], rcx
0000000100000e3d mov rcx, qword [__NSConcreteStackBlock_100001010]
0000000100000e44 lea rdi, qword [rbp+var_50]
0000000100000e48 mov qword [rdi], rcx
0000000100000e4b mov dword [rax+0x18], 0x12345678 ; 参数用一个结构体包了起来
0000000100000e52 mov ecx, 0xc2000000
0000000100000e57 mov qword [rdi+8], rcx
0000000100000e5b lea rcx, qword [___main_block_invoke]
0000000100000e62 mov qword [rdi+0x10], rcx
0000000100000e66 lea rcx, qword [___block_descriptor_tmp]
0000000100000e6d mov qword [rdi+0x18], rcx
0000000100000e71 mov qword [rdi+0x20], rax ; argument "instance" for method imp___stubs__objc_retainBlock
0000000100000e75 call imp___stubs__objc_retainBlock
0000000100000e7a mov rbx, rax
0000000100000e7d mov rdi, rbx
0000000100000e80 call qword [rbx+0x10]
0000000100000e83 mov rdi, rbx ; argument "instance" for method _objc_release
0000000100000e86 call qword [_objc_release_100001020] ; _objc_release
0000000100000e8c lea rdi, qword [rbp+var_28] ; argument #1 for method imp___stubs___Block_object_dispose
0000000100000e90 mov esi, 0x8 ; argument #2 for method imp___stubs___Block_object_dispose
0000000100000e95 call imp___stubs___Block_object_dispose
0000000100000e9a xor eax, eax
0000000100000e9c add rsp, 0x48
0000000100000ea0 pop rbx
0000000100000ea1 pop rbp
0000000100000ea2 ret
; endp
0000000100000ea3 mov rbx, rax
0000000100000ea6 lea rdi, qword [rbp-0x28]
0000000100000eaa mov esi, 0x8
0000000100000eaf call imp___stubs___Block_object_dispose
0000000100000eb4 mov rdi, rbx
0000000100000eb7 call imp___stubs___Unwind_Resume
0000000100000ebc ud2
___main_block_invoke:
0000000100000ebe push rbp ; DATA XREF=_main+68
0000000100000ebf mov rbp, rsp
0000000100000ec2 mov rax, qword [rdi+0x20]
0000000100000ec6 mov rax, qword [rax+8]
0000000100000eca mov esi, dword [rax+0x18]
0000000100000ecd lea rdi, qword [0x100000f94] ; "var = %d\\n", argument "format" for method imp___stubs__printf
0000000100000ed4 xor eax, eax
0000000100000ed6 pop rbp
0000000100000ed7 jmp imp___stubs__printf
-
mov dword [rax+0x18], 0x12345678
,可以看出,参数被传进结构体里了; -
mov qword [rdi+0x20], rax
,参数结构体的指针传递到block结构体; - 参数结构体在blockcopy的时候会复制到堆上,然后栈上结构体的forwarding会指向堆的结构体