我们都知道:
-
__block不能修饰全局变量, 静态变量
-
block内部修改外部auto变量的值需要__block修饰这个变量, block在被拷贝到堆上的同时也会使 __block变量拷贝到堆上, __block变量会被包装成一个结构体, 通过__forwarding指针访问结构体的val的值, 此时栈区的结构体内部的__forwarding指针会指向堆区的结构体
- 通过__forwarding指针可以实现无论是在block内部还是外部使用__block变量, 即__block变量配置在栈上还是堆上, 都可以顺利地访问同一个__block变量
从内存角度分析__block变量的访问过程
第一次断点打在没有堆区block引用__block变量的地方, a和c两个变量是为了更方便的在内存中查看__block变量的布局
看地址很难想象这三个变量在内存中是怎么布局的, 如果b没有__block修饰那么这三个变量的地址会是差值为4字节的连续的三个地址
Clang rewrite后的代码
struct __Block_byref_b_0 {
void *__isa;
__Block_byref_b_0 *__forwarding;
int __flags;
int __size;
int b; // val
};
int a = 1;
__attribute__((__blocks__(byref))) __Block_byref_b_0 b = {(void*)0,(__Block_byref_b_0 *)&b, 0, sizeof(__Block_byref_b_0), 2};
int c = 3;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_w__r01cw96d4wx2ll1z02jck5g00000gn_T_main_648eef_mi_0, &a);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_w__r01cw96d4wx2ll1z02jck5g00000gn_T_main_648eef_mi_1, &(b.__forwarding->b));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_w__r01cw96d4wx2ll1z02jck5g00000gn_T_main_648eef_mi_2, &c);
__block变量被包装成一个结构体, 根据结构体字节对齐共占据32个字节
可以看到b的获取方式&(b.__forwarding->b), 是通过__forwarding指针指向的结构体取成员val的值
看一下View Memory的内存布局:
栈区是从高地址向低地址连续分布, 小端低字节存低位
红色部分是变量a的1和变量c的3各4个字节, 还有因为字节对齐而空出的4个字节, 结构体成员最多占8个字节
中间未选中的部分就是变量b的内存布局:
struct __Block_byref_b_0 {
void *__isa; // 0
__Block_byref_b_0 *__forwarding; // 0x7FFEEA44DC60 即b在栈区开始的地址
int __flags;
int __size; // 16进制的20 = 10进制的32 即32个字节 这个结构体字节对齐后的size
int b; // val = 2
};
这里__forwarding指针为0x7FFEEA44DC60指向栈区的自己
计算可知打印的b的地址0x7ffeea44dc78为结构体中val的地址
过断点让堆区的block持有__block变量:
打印可知b的地址变为堆区的地址
再来看下View Memory的内存布局:
栈区
发现__forwarding指针变为0x6000032601E0, 指向堆区的拷贝
堆区
这里前32个字节就是__block变量在堆区上的布局
struct __Block_byref_b_0 {
void *__isa; // 0
__Block_byref_b_0 *__forwarding; // 0x6000032601E0 即b在堆区开始的地址
int __flags;
int __size; // 16进制的20 = 10进制的32 即32个字节 这个结构体字节对齐后的size
int b; // val = 2
};
这里__forwarding指针为0x6000032601E0指向堆区的自己
计算可知此时打印的b的地址0x6000032601f8 为结构体中val的地址
所以, 只要__block变量被拷贝到了堆区, 不管是在block内部还是外部, 访问的都是堆上的对象
至此就从内存角度分析了__block变量的内存布局, 访问__block变量的过程原理, 打印地址的真实意义