__block的深入研究

  • __block可以用于解决block内部无法修改auto变量值的问题

  • __block不能修饰全局变量、静态变量(static)

    • 编译器会将__block变量包装成一个对象

调用的是,从__Block_byref_a_0的指针找到 a所在的内存,然后修改值
Pasted image 20230726145216.png

第一层拷贝(block)

block中的第一层拷贝其实已经讲过了——_Block_copy将block从栈拷贝到堆

第二层拷贝(捕获变量的内存空间)

Pasted image 20230725201046.png

在函数声明时会传__main_block_desc_0_DATA结构体,在里面又会去调用__main_block_copy_0函数,__main_block_copy_0里面会调用_Block_object_assign——这就是第二层拷贝的调用入口。
根据flags & BLOCK_ALL_COPY_DISPOSE_FLAGS进到不同分支来处理捕获到的变量
Pasted image 20230726154645.png

此时捕获到的变量是被__block修饰的BLOCK_FIELD_IS_BYREF类型,就会调用*dest = _Block_byref_copy(object);

static struct Block_byref *_Block_byref_copy(const void *arg) {
    // 临时变量的保存
    struct Block_byref *src = (struct Block_byref *)arg;

    if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
        // src points to stack
        // 用原目标的大小在堆区生成一个Block_byref
        struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
        copy->isa = NULL;
        // byref value 4 is logical refcount of 2: one for caller, one for stack
        copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
        
        // 原来的区域和新的区域都指向同一个对象,使得block具备了修改能力
        copy->forwarding = copy; // patch heap copy to point to itself
        src->forwarding = copy;  // patch stack to point to heap copy
        copy->size = src->size;

        if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
            // Trust copy helper to copy everything of interest
            // If more than one field shows up in a byref block this is wrong XXX
            struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);
            struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);
            copy2->byref_keep = src2->byref_keep;
            copy2->byref_destroy = src2->byref_destroy;

            if (src->flags & BLOCK_BYREF_LAYOUT_EXTENDED) {
                struct Block_byref_3 *src3 = (struct Block_byref_3 *)(src2+1);
                struct Block_byref_3 *copy3 = (struct Block_byref_3*)(copy2+1);
                copy3->layout = src3->layout;
            }
            // 第三层拷贝
            (*src2->byref_keep)(copy, src);
        }
        else {
            // Bitwise copy.
            // This copy includes Block_byref_3, if any.
            memmove(copy+1, src+1, src->size - sizeof(*src));
        }
    }
    // already copied to heap
    else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
        latching_incr_int(&src->forwarding->flags);
    }
    
    return src->forwarding;
}

用原目标name的大小在堆区生成一个Block_byref
copy->forwarding = copy; & src->forwarding = copy;——原来的区域和新的区域都指向同一个对象,使得block具备了修改能力
(* src2->byref_keep)(copy, src)开始第三层拷贝

第三层拷贝(拷贝对象)

(*src2->byref_keep)(copy, src)点进去会来到Block_byref结构来,而byref_keepBlock_byref的第5个属性

struct Block_byref {
    void * __ptrauth_objc_isa_pointer isa;
    struct Block_byref *forwarding;
    volatile int32_t flags; // contains ref count
    uint32_t size;
};

struct Block_byref_2 {
    // requires BLOCK_BYREF_HAS_COPY_DISPOSE
    BlockByrefKeepFunction byref_keep;
    BlockByrefDestroyFunction byref_destroy;
};

struct Block_byref_3 {
    // requires BLOCK_BYREF_LAYOUT_EXTENDED
    const char *layout;
};

Pasted image 20230726154841.png

第5位就等于byref_keep,所以在第二层拷贝时会调用__Block_byref_id_object_copy_131

static void __Block_byref_id_object_copy_131(void *dst, void *src) {
 _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void *src) {
 _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}

这个(char*)dst + 40看到__Block_byref_name_0就顿悟了,刚好取得变量name对象。

struct __Block_byref_name_0 {
  void *__isa;
__Block_byref_name_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSString *name;
};

_Block_object_assign在对BLOCK_FIELD_IS_OBJECT情况时会做出如下操作:

	case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
    case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
        /*******
         // copy the actual field held in the __block container
         // Note this is MRC unretained __block only. 
         // ARC retained __block is handled by the copy helper directly.
         __block id object;
         __block void (^object)(void);
         [^{ object; } copy];
         ********/

      *dest = object;
      break;

block捕获的外接变量由ARC自动管理,捕获到name进行拷贝
block中有三层拷贝:拷贝block、拷贝捕获变量的内存地址、拷贝对象

_ Block_object_dispose

void _Block_object_dispose(const void *object, const int flags) {
    switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
      case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
      case BLOCK_FIELD_IS_BYREF:
        // get rid of the __block data structure held in a Block
        _Block_byref_release(object);
        break;
      case BLOCK_FIELD_IS_BLOCK:
        _Block_release(object);
        break;
      case BLOCK_FIELD_IS_OBJECT:
        _Block_release_object(object);
        break;
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK  | BLOCK_FIELD_IS_WEAK:
        break;
      default:
        break;
    }
}

// API entry point to release a copied Block
void _Block_release(const void *arg) {
    struct Block_layout *aBlock = (struct Block_layout *)arg;
    if (!aBlock) return;
    if (aBlock->flags & BLOCK_IS_GLOBAL) return;
    if (! (aBlock->flags & BLOCK_NEEDS_FREE)) return;

    if (latching_decr_int_should_deallocate(&aBlock->flags)) {
        _Block_call_dispose_helper(aBlock);
        _Block_destructInstance(aBlock);
        free(aBlock);
    }
}

  • 如果是释放对象就什么也不做(自动释放)
  • 如果是__block修饰,就将指向指回原来的区域并使用free释放
<think>嗯,用户正在深入探究Windows CRT堆分配机制中的一个特定表达式"__acrt_first_block ==header"。这问题相当底层,看来用户可能在调试内存问题或研究CRT源码。 从用户引用的资料片段看,用户应该已经接触过CRT内存管理的部分实现细节。引用[2]讨论的获取程序计数器的方法,暗示用户可能在研究堆栈跟踪或内存分配溯源。而引用[1]提到的函数对象和调用链,可能和CRT的初始化回调机制有关。用户真正想知道的可能是:这个表达式在调试堆(debug heap)中的作用是什么?为什么比较第一个块这么重要?这涉及到CRT内存分配器的自我保护机制。让我拆解这个表达式:1. __acrt_first_block是CRT内部维护的指针,指向堆上分配的第一个内存块2."header"指当前操作的内存块头结构3.这个比较用于验证当前块是否是初始分配块关键点在于:当调试堆检测到首块被异常修改时,会立即断言。因为首块存储着关键的管理信息,如果被破坏,说明整个堆可能已不可信。 需要强调调试堆的特殊性:在_DEBUG模式下,每个分配块都有额外头部(32字节前缀),包含:-分配序号(Allocation number) -文件名/行号(如果记录了的话) -保护间隙(noman's land) -块类型标记(普通块/客户端块/CRT内部块) 特别要注意,当__acrt_first_block被意外覆盖时,CRT会触发__acrt_report_block_type_mismatch,因为首块承载着管理其他调试块的关键信息。这通常意味着发生了以下情况之一:-对未分配内存的写入-分配头部的缓冲区溢出-双重释放破坏头部用户可能在实际调试中遇到了_HEAP_CORRUPTION错误,这个表达式就是触发点之一。建议用户检查内存写入边界,特别是对分配块之前区域的写操作。考虑到用户的技术水平,可以补充说明:在Release构建中这个检查不存在,但调试堆的这些额外检查正是发现内存错误的利器。</think>理解Windows CRT堆分配中的`__acrt_first_block == header`表达式需要分析其调试机制和安全验证逻辑: ### 表达式作用 `__acrt_first_block == header`是Microsoft CRT(C Runtime)调试堆(debug heap)中的关键验证逻辑,用于检测**内存块头部的非法篡改**。其核心机制如下: 1. **首次分配标记** - `__acrt_first_block`是全局指针,指向调试堆中**首个分配的内存块头部** - 当程序首次调用`malloc`/`new`时,CRT会记录该块的头部地址到`__acrt_first_block` 2. **头部结构验证** 在释放内存(`free`/`delete`)时,CRT执行安全检查: ```c if (__acrt_first_block == header) { // 验证头部结构是否被破坏 if (header->block_type != _BLOCK_TYPE_HEAD || header->guard_bytes != _GUARD_BYTES) { _CRT_DEBUG_REPORT(_CRT_ERROR, "Heap corruption detected!"); } } ``` - **`header`**:当前释放内存块的头部指针 - 若当前块是首个分配块(`__acrt_first_block == header`),则检查其头部关键字段: * `block_type`:必须为`_BLOCK_TYPE_HEAD`(标识基础分配块) * `guard_bytes`:必须匹配预设的魔数(如`0xFD`填充的保护字节) ### 技术原理 1. **调试堆结构** 调试模式下分配的每个内存块包含隐藏头部: ``` +---------+----------------+---------+ | Header | 用户可用内存区 | Guard | | (32字节)| (实际请求大小) | (4字节) | +---------+----------------+---------+ ``` - **Header**:存储分配信息(大小、文件名、行号等) - **Guard区域**:填充`0xFD`用于溢出检测 2. **首块的特殊性** - 首个分配块承载CRT堆管理元数据 - 其头部被破坏会导致整个堆状态不可信 - `__acrt_first_block`作为"堆完整性锚点" ### 触发场景 当表达式验证失败时,通常会抛出`_HEAP_CORRUPTION`错误,常见原因: 1. **缓冲区溢出** 用户数据覆盖了头部/保护区域(如数组越界) ```cpp char* p = new char[10]; p[10] = 'x'; // 覆盖Guard区域 → 触发验证失败 ``` 2. **双重释放** 重复释放同一内存导致头部被标记为"已释放" 3. **野指针操作** 释放后仍修改内存内容 ### 调试建议 若遇到相关错误: 1. 启用Page Heap验证器: ```bash gflags.exe /p /enable yourapp.exe /full ``` 2. 使用CRT调试函数: ```cpp _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_DEBUG); ``` 3. 检查内存操作边界(如`strcpy_s`替代`strcpy`) > 此机制体现了CRT对堆完整性的保护:首个内存块作为"信任根",其头部完整性直接影响后续所有堆操作的可靠性[^1][^2]。 --- ### 相关问题 1. Windows CRT调试堆如何检测缓冲区溢出? 2. 如何解读`_BLOCK_TYPE_IS_VALID`断言失败? 3. Release模式下CRT堆管理有何不同? 4. 如何定位由堆损坏导致的随机崩溃问题? [^1]: 调试堆通过保护字节(Guard Bytes)和头部校验和检测内存覆盖。 [^2]: 首个内存块的头部存储堆初始化信息,其破坏会级联影响后续操作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值