惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Google DeepMind News
Google DeepMind News
The Last Watchdog
The Last Watchdog
腾讯CDC
Apple Machine Learning Research
Apple Machine Learning Research
有赞技术团队
有赞技术团队
Last Week in AI
Last Week in AI
量子位
雷峰网
雷峰网
宝玉的分享
宝玉的分享
美团技术团队
阮一峰的网络日志
阮一峰的网络日志
博客园 - 叶小钗
P
Privacy International News Feed
NISL@THU
NISL@THU
V
V2EX
博客园 - 三生石上(FineUI控件)
T
The Exploit Database - CXSecurity.com
T
Threat Research - Cisco Blogs
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
IT之家
IT之家
T
Tor Project blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
T
Threatpost
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
大猫的无限游戏
大猫的无限游戏
V
Vulnerabilities – Threatpost
V
Visual Studio Blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
The Hacker News
The Hacker News
Scott Helme
Scott Helme
H
Hacker News: Front Page
罗磊的独立博客
Hacker News - Newest:
Hacker News - Newest: "LLM"
K
Kaspersky official blog
S
Secure Thoughts
www.infosecurity-magazine.com
www.infosecurity-magazine.com
The Cloudflare Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
C
Cybersecurity and Infrastructure Security Agency CISA
小众软件
小众软件
月光博客
月光博客
人人都是产品经理
人人都是产品经理
AI
AI
Help Net Security
Help Net Security
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
酷 壳 – CoolShell
酷 壳 – CoolShell
Webroot Blog
Webroot Blog
博客园 - 【当耐特】
PCI Perspectives
PCI Perspectives
A
Arctic Wolf

博客园 - chaoguo1234

如何自定义 UIScrollView 的减速系数 Plist 二进制格式 UIImageView 设置图片不生效的原因排查 Any metadata 的内存布局 Mach-O 文件结构 Block Copy 的内存布局详解 Block 内存布局详解 XNU Inside: iOS 模拟器 WebKit Insie: WebKit 调试(二) NSMutableDictionary 的内存布局 NSDictionary 内存布局 WebKit Inside: GPU 进程调试 WebKit Inside: 渲染树 WebKit Inside: px 与 pt WebKit Inside: CSS 的匹配原理 dyld: 神秘的 __dso_handle 一文搞懂 ARM 64 系列: ADD(立即数版) 一文搞懂 ARM 64: AUTIBSP 一文搞懂 ARM 64 系列: PACISB XCode 编译 PAG 源码 一文搞懂 ARM 64 系列: 一文搞懂 ARM 64 系列: 函数调用传参与返回值 一文搞懂 ARM 64 系列: 寄存器
__block 变量内存布局详解
chaoguo1234 · 2026-03-19 · via 博客园 - chaoguo1234

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变量时,它的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)
};

__block flags

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的形式。

其中x y z都是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出来的,那么就会设置这个标志。