1 内存布局
按照LLVM工程源码Block_private.h中的定义,__block变量的内存布局如下:
c
struct Block_byref {
void *isa;
struct Block_byref *forwarding;
int flags;
int size;
// 可选
void (*byref_keep)(struct Block_byref *dst, struct Block_byref *src);
// 可选
void (*byref_destroy)(struct Block_byref *);
// 可选
void *variable_layout;
// 变量
Type variable;
};
isa通常会被赋值为0,后面会有介绍。
forwarding指针在__block变量定义时会指向Block_byref自身。
当Block发生copy时它的值会有变动,会放到Block的copy中写。
flags是标志位,后面会有介绍。
size是当前结构体所占用的字节大小。
byref_keep与Block的copy相关,只有在满足条件时才有,后面会有介绍。
byref_destory与Block的释放相关,只有在满足条件时才有,后面会有介绍。
variable_layout在__block修饰结构体Struct时才会有,后面会介绍。
variable是被定义成__block的变量。
从上图可以看到,当一个Block捕获了一个__block变量时,它的Block_Descriptor中会有copy_helper和dispose_helper。
因为Block_byref也有isa指针,虽然它不能作为一个OC对象看待,但是从结构上看,也符合BLOCK_HAS_COPY_DISPOSE被设置的条件。
但是结构体Block_byref中的byref_keep和byref_destroy仍是可选的。
下面用一个例子来直观感受一下:
objectivec
void blockTest() {
// __block 变量
__block int bi = 4;
void(^blk)(int, int, int) = ^(int i, int j, int k) {
int result = i + j + k + bi;
NSLog(@"%d", result);
};
// block 外操作 bi
bi++;
}
变量bi是一个__block变量。
使用clang的rewirte-objc将上面的代码重写为c++代码如下:
cpp
void blockTest() {
// __block 变量
__attribute__((__blocks__(byref))) __Block_byref_bi_0 bi = {(void*)0,(__Block_byref_bi_0 *)&bi, 0, sizeof(__Block_byref_bi_0), 4};
void(*blk)(int, int, int) = ((void (*)(int, int, int))&__blockTest_block_impl_0((void *)__blockTest_block_func_0, &__blockTest_block_desc_0_DATA, (__Block_byref_bi_0 *)&bi, 570425344));
// block 外操作 bi
(bi.__forwarding->bi)++;
}
可以看到__block变量被编译后,成为了Block_byref结构体:
cpp
struct __Block_byref_bi_0 {
void *__isa;
__Block_byref_bi_0 *__forwarding;
int __flags;
int __size;
int bi;
};
即使在Block外部访问bi变量,也是通过这个结构体的forwarding指针进行访问。
在Block内部访问bi变量,也是通过forwarding指针:
cpp
struct __blockTest_block_impl_0 {
struct __block_impl impl;
struct __blockTest_block_desc_0* Desc;
// Block 捕获的 __block 变量 bi
__Block_byref_bi_0 *bi; // by ref
};
static void __blockTest_block_func_0(struct __blockTest_block_impl_0 *__cself, int i, int j, int k) {
__Block_byref_bi_0 *bi = __cself->bi; // bound by ref
// Block 内部访问 bi 变量
int result = i + j + k + (bi->__forwarding->bi);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_p6_jy49zvqx2656qb8rbq64hc_w0000gn_T_Block_de3226_mi_0, result);
}
forwarding指针的作用会放到Block的copy中写。
2 isa
__block变量结构体的isa指针会固定设置为0。
3 flags
在LLVM工程源码CGBlocks.h中定义了flags:
c
// Flags stored in __block variables.
enum BlockByrefFlags {
BLOCK_BYREF_HAS_COPY_DISPOSE = (1 << 25), // compiler
BLOCK_BYREF_LAYOUT_MASK = (0xF << 28), // compiler
BLOCK_BYREF_LAYOUT_EXTENDED = (1 << 28),
BLOCK_BYREF_LAYOUT_NON_OBJECT = (2 << 28),
BLOCK_BYREF_LAYOUT_STRONG = (3 << 28),
BLOCK_BYREF_LAYOUT_WEAK = (4 << 28),
BLOCK_BYREF_LAYOUT_UNRETAINED = (5 << 28)
};
3.1 BLOCK_BYREF_HAS_COPY_DISPOSE
设置这个标志的条件和Block结构体类似:
1 __block变量本身就是一个结构体;
2 __block变量是一个OC对象;
3 __block变量是一个C++对象。
具体的设置规则,可以参看LLVM源码CGBlocks.cpp中的函数:
cpp
BlockByrefHelpers *
CodeGenFunction::buildByrefHelpers(llvm::StructType &byrefType,
const AutoVarEmission &emission)
只有能成功创建helper函数的Block_byref结构体,才会设置这个标志。
具体代码可以参看LLVM源码CGBlocks.cpp中的函数:
cpp
void CodeGenFunction::emitByrefStructureInit(const AutoVarEmission &emission) {
...
// Build the byref helpers if necessary. This is null if we don't need any.
BlockByrefHelpers *helpers = buildByrefHelpers(*byrefType, emission);
...
BlockFlags flags;
if (helpers) flags |= BLOCK_BYREF_HAS_COPY_DISPOSE;
...
}
3.2 BLOCK_BYREF_LAYOUT_MASK
这是一个4bit的掩码,可以从flags中取出取出对应的枚举字段。
3.3 BLOCK_BYREF_LAYOUT_EXTEND
如果__block变量是一个结构体或者是一个C++对象(C++里结构体和class可以互用),那么这个标志会被设置。
同时,Block_byref结构体中的variable_layout会有值。
variable_layout描述了被捕获的结构体中,各个成员变量的类型以及占用的字节数。
对于variable_layout的解析虽然有点复杂,但是实际测试中并没有发现它的用处,即使在Block拷贝时也没有用。
有兴趣的可以继续往下看。
比如有如下的结构体:
objectivec
typedef struct S {
int i;
X *x; // X 是 OC 对象
};
void blockTest() {
__block S s;
s.i = 1;
s.x = [X new];
void(blk)(int, int, int) = ^(int i, int j, int k) {
int result = i + j + k + s.i;
NSLog(@"%@", result);
};
}
上面代码中的Block捕获了一个结构体__block S。
我们使用lldb打印对应的内存值进行查看:
bash
(lldb) po [blk description]
<__NSMallocBlock__: 0x600000c00090>
# Block 内存值
(lldb) x/8g 0x600000c00090
0x600000c00090: 0x00000001f2d7bb78 0x00000000c3000002
0x600000c000a0: 0x00000001048c80dc 0x00000001048d4240
0x600000c000b0: 0x0000600001700100
# Block_byref 内存值
(lldb) x/8g 0x0000600001700100
0x600001700100: 0x0000000000000000 0x0000600001700100
0x600001700110: 0x0000004013000004 0x00000001048c800c
0x600001700120: 0x00000001048c8070 0x00000001048ce87a
0x600001700130: 0x0000000000000001 0x00006000000150a0
# variable_layout 内存值
(lldb) x/8g 0x00000001048ce87a
0x1048ce87a: 0x6b636f6c42003020
Block_byref内存值0x0000004013000004就是size+flags。
分析0x0000004013000004的低32bit,它的第28bit正好是1,说明BLOCK_BYREF_LAYOUT_EXTEND被设置了。
接下来再看variable_layout。
variable_layout是一个指针,指向的内存中的值是0x6b636f6c42003020。
对于0x6b636f6c42003020,需要从右向左一个字节一个字节的解析,直到遇到00为止。
每一个字节的高4bit定义了数据的类型,低4bit定义了数据占用的字节数。
高4bit的枚举值定义在LLVM源码CGObjcCMac.cpp中:
cpp
enum BLOCK_LAYOUT_OPCODE {
/// An operator which affects how the following layout should be
/// interpreted.
/// I == 0: Halt interpretation and treat everything else as
/// a non-pointer. Note that this instruction is equal
/// to '\0'.
/// I != 0: Currently unused.
BLOCK_LAYOUT_OPERATOR = 0,
/// The next I+1 bytes do not contain a value of object pointer type.
/// Note that this can leave the stream unaligned, meaning that
/// subsequent word-size instructions do not begin at a multiple of
/// the pointer size.
BLOCK_LAYOUT_NON_OBJECT_BYTES = 1,
/// The next I+1 words do not contain a value of object pointer type.
/// This is simply an optimized version of BLOCK_LAYOUT_BYTES for
/// when the required skip quantity is a multiple of the pointer size.
BLOCK_LAYOUT_NON_OBJECT_WORDS = 2,
/// The next I+1 words are __strong pointers to Objective-C
/// objects or blocks.
BLOCK_LAYOUT_STRONG = 3,
/// The next I+1 words are pointers to __block variables.
BLOCK_LAYOUT_BYREF = 4,
/// The next I+1 words are __weak pointers to Objective-C
/// objects or blocks.
BLOCK_LAYOUT_WEAK = 5,
/// The next I+1 words are __unsafe_unretained pointers to
/// Objective-C objects or blocks.
BLOCK_LAYOUT_UNRETAINED = 6
/// The next I+1 words are block or object pointers with some
/// as-yet-unspecified ownership semantics. If we add more
/// flavors of ownership semantics, values will be taken from
/// this range.
///
/// This is included so that older tools can at least continue
/// processing the layout past such things.
// BLOCK_LAYOUT_OWNERSHIP_UNKNOWN = 7..10,
/// All other opcodes are reserved. Halt interpretation and
/// treat everything else as opaque.
};
上面枚举中的word-size是一个指针真用的字节数,64bit的机器上通常是8字节:
cpp
// CGObjcMac.cpp
llvm::Constant *CGObjCCommonMac::getBitmapBlockLayout(bool ComputeByrefLayout) {
...
// Word Size 的大小
unsigned WordSizeInBits = CGM.getTarget().getPointerWidth(LangAS::Default);
...
}
现在让我们从右向左解析0x6b636f6c42003020。
第一个字节是20,高4bit是2,代表BLOCK_LAYOUT_NON_OBJECT_WORDS。
这个值说明当前数据类型是一个非对象类型,参考结构体S的定义,符合预期。
低4bit是0,参考BLOCK_LAYOUT_NON_OBJECT_WORDS中的注释,它代表接下来\((0 + 1) * word\_size\)个字节都是非对象类型。
第二个字节是30,高4bit是3,代表BLOCK_LAYOUT_STRONG。
这个值说明说明当前数据类型是一个OC对象Strong指针,参考结构体S的定义,符合预期。
低4bit时0,参考BLOCK_LAYOUT_STRONG中的注释,它代表接下来\((0 + 1) * word\_size\)个字节都是OC对象Strong指针,也就是占用8字节。
第三个字节是00,高4bit是0,代表BLOCK_LAYOUT_OPERATOR。
这个值说明解析应该停止。
这种情形下,低4bit总是0。
如果被捕获的结构体比较大,则需要解析连续的内存值。
比如有下面的结构体:
cpp
typedef struct S {
int ai;
BlockX *a;
int bi;
BlockX *b;
int ci;
BlockX *c;
int di;
BlockX *d;
int ei;
BlockX *e;
};
使用lldb查看variable_layout的内存值为:
bash
(lldb) x/8g 0x000000010017aa1a
0x10017aa1a: 0x3020302030203020 0x6b636f6c42003020
解析的时候,从第一条内存最右边开始,然后接着是第二条内存最右边,直到遇到00结束。
variable_layout的值还有一种内联形式。
如果一个结构体或者C++对象里只有enum BLOCK_LAYOUT_OPCODE中定义的如下类型的成员变量:
BLOCK_LAYOUT_STRONGBLOCK_LAYOUT_BYREFBLOCK_LAYOUT_WEAK
并且每种类型的成员变量都是按顺序连续分布,那么variable_layout就会使用内联形式。
variable_layout指针本身的值,就会变成0x0000000000000xyz的形式。
其中x y z都是4bit。
x表示BLOCK_LAYOUT_STRONG的个数;
y表示BLOCK_LAYOUT_BYREF的个数;
z表示BLOCK_LAYOUT_WEAK的个数。
但是如果variable_layout的值大于等于(1 << 12),那么还是会回退到普通的情形。
比如下面代码中:
cpp
typedef struct S {
BLOCK_LAYOUT_STRONG *x1;
BLOCK_LAYOUT_STRONG *x2;
BLOCK_LAYOUT_BYREF *y1;
BLOCK_LAYOUT_BYREF *y2;
BLOCK_LAYOUT_WEAK *z1;
BLOCK_LAYOUT_WEAK *z2;
};
就可以使用variable_layout的内联形式, variable_layout本身的值为0x0000000000000222。
如果某种类型没有,那么variable_layout中对应位置就是0,比如:
cpp
typedef struct S {
BLOCK_LAYOUT_STRONG *x1;
BLOCK_LAYOUT_STRONG *x2;
BLOCK_LAYOUT_WEAK *z1;
BLOCK_LAYOUT_WEAK *z2;
};
那么variable_layout本身的值为0x0000000000000202。
如果类型出现的顺序不对,variable_layout也不会使用内联形式,比如:
cpp
typedef struct S {
BLOCK_LAYOUT_BYREF *y1;
BLOCK_LAYOUT_BYREF *y2;
BLOCK_LAYOUT_STRONG *x1;
BLOCK_LAYOUT_STRONG *x2;
BLOCK_LAYOUT_WEAK *z1;
BLOCK_LAYOUT_WEAK *z2;
};
那么variable_layout仍使用普通形式。
有关variable_layout的内联形式,定义在LLVM源码的CGObjcMac.cpp中:
cpp
/// InlineLayoutInstruction - This routine produce an inline instruction for the
/// block variable layout if it can. If not, it returns 0. Rules are as follow:
/// If ((uintptr_t) layout) < (1 << 12), the layout is inline. In the 64bit
/// world, an inline layout of value 0x0000000000000xyz is interpreted as
/// follows: x captured object pointers of BLOCK_LAYOUT_STRONG. Followed by y
/// captured object of BLOCK_LAYOUT_BYREF. Followed by z captured object of
/// BLOCK_LAYOUT_WEAK. If any of the above is missing, zero replaces it. For
/// example, 0x00000x00 means x BLOCK_LAYOUT_STRONG and no BLOCK_LAYOUT_BYREF
/// and no BLOCK_LAYOUT_WEAK objects are captured.
uint64_t CGObjCCommonMac::InlineLayoutInstruction(
SmallVectorImpl<unsigned char> &Layout)
方法上面的注释已经解释的很清楚了。
3.4 BLOCK_BYREF_LAYOUT_NON_OBJECT
当__block变量是一个非对象类型,比如int时,会设置这个标志。
3.5 BLOCK_BYREF_LAYOUT_STRONG
当__block变量是一个Strong类型的OC变量时,会设置这个标志。
3.6 BLOCK_BYREF_LAYOUT_WEAK
当__block变量是一个Weak类型的OC变量时,会设置这个标志。
objectivec
__block __weak X *x;
3.7 BLOCK_BYREF_LAYOUT_UNSAFE_UNRETAINED
当__block变量被__unsafe_unretained修饰时,会设置这个标志。
objectivec
__block __unsafe_unretained X *x;
3.8 Other Flags
除了enum BlockByrefFlags中定义的枚举之外,还有其他两个被使用的枚举:
cpp
emum {
BLOCK_REFCOUNT_MASK = (0xffff),
BLOCK_NEEDS_FREE = (1 << 24),
}
这两个枚举和Block中的flags是通用的。
3.9 BLOCK_REFCOUNT_MASK
表示引用计数计数掩码。
也就是说,int类型的flags并不是32bit都是作为标志,最低16bit用来表示Block_byref被引用的次数retainCount。
cpp
block_byref_retain_count = flags & BLOCK_REFCOUNT_MASK
3.10 BLOCK_NEEDS_FREE
当一个Block_byref是copy出来的,那么就会设置这个标志。