news 2026/7/17 22:53:55

__block 变量内存布局详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
__block 变量内存布局详解

1 内存布局

按照LLVM工程源码Block_private.h中的定义,__block变量的内存布局如下:

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时它的值会有变动,会放到Blockcopy中写。

flags是标志位,后面会有介绍。

size是当前结构体所占用的字节大小。

byref_keepBlockcopy相关,只有在满足条件时才有,后面会有介绍。

byref_destoryBlock的释放相关,只有在满足条件时才有,后面会有介绍。

variable_layout__block修饰结构体Struct时才会有,后面会介绍。

variable是被定义成__block的变量。

从上图可以看到,当一个Block捕获了一个__block变量时,它的Block_Descriptor中会有copy_helperdispose_helper

因为Block_byref也有isa指针,虽然它不能作为一个OC对象看待,但是从结构上看,也符合BLOCK_HAS_COPY_DISPOSE被设置的条件。

但是结构体Block_byref中的byref_keepbyref_destroy仍是可选的。

下面用一个例子来直观感受一下:

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变量。

使用clangrewirte-objc将上面的代码重写为c++代码如下:

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结构体:

struct __Block_byref_bi_0 { void *__isa; __Block_byref_bi_0 *__forwarding; int __flags; int __size; int bi; };

即使在Block外部访问bi变量,也是通过这个结构体的forwarding指针进行访问。

Block内部访问bi变量,也是通过forwarding指针:

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指针的作用会放到Blockcopy中写。

2 isa

__block变量结构体的isa指针会固定设置为0

3 flags

LLVM工程源码CGBlocks.h中定义了flags:

// 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中的函数:

BlockByrefHelpers * CodeGenFunction::buildByrefHelpers(llvm::StructType &byrefType, const AutoVarEmission &emission)

只有能成功创建helper函数的Block_byref结构体,才会设置这个标志。

具体代码可以参看LLVM源码CGBlocks.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拷贝时也没有用。

有兴趣的可以继续往下看。

比如有如下的结构体:

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打印对应的内存值进行查看:

(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中:

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字节:

// CGObjcMac.cpp llvm::Constant *CGObjCCommonMac::getBitmapBlockLayout(bool ComputeByrefLayout) { ... // Word Size 的大小 unsigned WordSizeInBits = CGM.getTarget().getPointerWidth(LangAS::Default); ... }

现在让我们从右向左解析0x6b636f6c42003020

第一个字节是20,高4bit2,代表BLOCK_LAYOUT_NON_OBJECT_WORDS

这个值说明当前数据类型是一个非对象类型,参考结构体S的定义,符合预期。

4bit0,参考BLOCK_LAYOUT_NON_OBJECT_WORDS中的注释,它代表接下来\((0 + 1) * word\_size\)个字节都是非对象类型。

第二个字节是30,高4bit3,代表BLOCK_LAYOUT_STRONG

这个值说明说明当前数据类型是一个OC对象Strong指针,参考结构体S的定义,符合预期。

4bit0,参考BLOCK_LAYOUT_STRONG中的注释,它代表接下来\((0 + 1) * word\_size\)个字节都是OC对象Strong指针,也就是占用8字节。

第三个字节是00,高4bit0,代表BLOCK_LAYOUT_OPERATOR

这个值说明解析应该停止。

这种情形下,低4bit总是0

如果被捕获的结构体比较大,则需要解析连续的内存值。

比如有下面的结构体:

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的内存值为:

(lldb) x/8g 0x000000010017aa1a 0x10017aa1a: 0x3020302030203020 0x6b636f6c42003020

解析的时候,从第一条内存最右边开始,然后接着是第二条内存最右边,直到遇到00结束。

variable_layout的值还有一种内联形式。

如果一个结构体或者C++对象里只有enum BLOCK_LAYOUT_OPCODE中定义的如下类型的成员变量:

  • BLOCK_LAYOUT_STRONG
  • BLOCK_LAYOUT_BYREF
  • BLOCK_LAYOUT_WEAK

并且每种类型的成员变量都是按顺序连续分布,那么variable_layout就会使用内联形式。

variable_layout指针本身的值,就会变成0x0000000000000xyz的形式。

其中xyz都是4bit

x表示BLOCK_LAYOUT_STRONG的个数;

y表示BLOCK_LAYOUT_BYREF的个数;

z表示BLOCK_LAYOUT_WEAK的个数。

但是如果variable_layout的值大于等于(1 << 12),那么还是会回退到普通的情形。

比如下面代码中:

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,比如:

typedef struct S { BLOCK_LAYOUT_STRONG *x1; BLOCK_LAYOUT_STRONG *x2; BLOCK_LAYOUT_WEAK *z1; BLOCK_LAYOUT_WEAK *z2; };

那么variable_layout本身的值为0x0000000000000202

如果类型出现的顺序不对,variable_layout也不会使用内联形式,比如:

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中:

/// 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变量时,会设置这个标志。

__block __weak X *x;

3.7 BLOCK_BYREF_LAYOUT_UNSAFE_UNRETAINED

__block变量被__unsafe_unretained修饰时,会设置这个标志。

__block __unsafe_unretained X *x;

3.8 Other Flags

除了enum BlockByrefFlags中定义的枚举之外,还有其他两个被使用的枚举:

emum { BLOCK_REFCOUNT_MASK = (0xffff), BLOCK_NEEDS_FREE = (1 << 24), }

这两个枚举和Block中的flags是通用的。

3.9 BLOCK_REFCOUNT_MASK

表示引用计数计数掩码。

也就是说,int类型的flags并不是32bit都是作为标志,最低16bit用来表示Block_byref被引用的次数retainCount

block_byref_retain_count = flags & BLOCK_REFCOUNT_MASK

3.10 BLOCK_NEEDS_FREE

当一个Block_byrefcopy出来的,那么就会设置这个标志。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/17 22:52:59

51单片机低功耗模式全解析:如何用掉电模式让纽扣电池续航翻倍?

51单片机低功耗实战&#xff1a;从理论到纽扣电池续航倍增方案 在物联网终端设备开发中&#xff0c;电源管理往往是决定产品成败的关键因素之一。想象一下&#xff0c;一款依靠纽扣电池供电的智能门锁&#xff0c;如果因为程序设计不当导致电量在几个月内耗尽&#xff0c;不仅会…

作者头像 李华
网站建设 2026/7/14 14:07:37

对比学习+持续学习=?UCAD框架在PyTorch中的复现与魔改心得

UCAD框架深度解析&#xff1a;对比学习与持续学习的协同进化 在工业质检领域&#xff0c;异常检测技术正面临前所未有的挑战——产线需要同时处理数十种产品型号的检测任务&#xff0c;而传统方法要求为每个型号单独训练模型。这不仅消耗大量计算资源&#xff0c;更导致模型维护…

作者头像 李华
网站建设 2026/7/14 14:07:57

Table render函数里边,判断该字段是否可点击样式区分

{name: manageIp,label: 管理地址,width: 150,render: (h, params) > {if (this.searchForm.tabActive 1) {return h(a,{class: aColor,attrs: { href: javascript:void(0) },style: { color: #409EFF, cursor: pointer }, // 自定义样式on: { click: () > this.manageI…

作者头像 李华
网站建设 2026/7/14 14:07:53

RocketMQ 事务消息实现及核心使用场景(完整实战指南)

RocketMQ 事务消息是解决分布式事务的核心方案&#xff0c;基于「两阶段提交 消息回查」机制&#xff0c;保证「本地事务」与「消息发送」的最终一致性。 一、事务消息核心原理&#xff08;先理解再上手&#xff09; 1. 分布式事务痛点 在跨服务/跨库场景中&#xff08;如「下…

作者头像 李华
网站建设 2026/7/14 14:07:56

别再写杂乱代码了!Python 模块与包的正确使用姿势

Python模块与包教程 1. 模块的概念 在Python中&#xff0c;模块(Module)是一个包含Python定义和语句的文件&#xff0c;文件名就是模块名加上.py后缀。使用模块可以将相关代码组织在一起&#xff0c;提高代码的可维护性和复用性。 模块的优势 提高代码可维护性&#xff1a;将…

作者头像 李华