閉包是現代編程語言的標配,C 語言通過 Apple 推出的 Block 擴展(`^` 語法)實現,C++11 引入 Lambda 表達式(`[]` 語法)。本文從語法捕獲、修改外部變量、底層實現、內存管理四個維度,對比兩者異同,並揭示 `__block` 與 `&` 引用捕獲的本質區別。
// Objective-C Block
^{ printf("Hello, World!\n"); }();
// C++11 Lambda
[] { cout << "Hello, World" << endl; }();
編譯器會為每個閉包生成唯一函數名:`__{$Scope}_block_func_{$index}`,如 `__main_block_func_0`。
| 場景 | Block (Objective-C) | C++11 Lambda |
|---|---|---|
| 默認捕獲 | 值捕獲 (唯讀) | 不可捕獲 (需顯式指定) |
| 值捕獲語法 | 自動 | `[=]` |
| 引用捕獲語法 | `__block` 修飾變量 | `[&]` 或 `[&var]` |
| 修改外部變量 | 需 `__block int i;` | 需 `[&] { i=0; }` |
Block 默認按值拷貝外部變量,C++ 默認不捕獲,強制開發者明確語義,避免隱性 Bug。
編譯器將 Block 降級為 C 結構體 + 函數指針:
struct __block_impl {
void *isa; // 類似 C++ vptr / Python PyObject,納入 OC 對象體系
int Flags;
int Reserved;
void *FuncPtr; // 指向實際執行函數
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0 *Desc;
int i; // 捕獲的變量 (值拷貝)
// 構造函數初始化 isa、FuncPtr、Desc、i
};
執行本質:`((void(*)(struct __block_impl*))(block->FuncPtr))(block)` —— 以結構體自身作 `self` 參數調用函數指針。
加上 `__block` 後,變量 `i` 變身為獨立結構體:
struct __Block_byref_i_0 {
void *__isa;
__Block_byref_i_0 *__forwarding; // 關鍵:指向自身或堆上拷貝
int __flags;
int __size;
int i; // 真實值存放處
};
Block 結構體改存 `__Block_byref_i_0 *i`,訪問時雙重間接:`i->__forwarding->i`。
| 維度 | Block (Clang/Apple) | C++11 Lambda |
|---|---|---|
| 語法標記 | `^` | `[]` |
| 默認捕獲 | 值捕獲 (readonly) | 無 (需顯式 `[=]`/`[&]`) |
| 可變捕獲 | `__block` 變量 | `[&]` 引用捕獲 |
| 實現機制 | 結構體 + 函數指針 + `__forwarding` 雙重間接 | 仿函數類 (仿函數對象),成員存捕獲變量 |
| 內存管理 | 手動 `copy`/`release` (ARC 自動) | RAII,隨對象生命週期自動析構 |
| 適用場景 | OC/GCD 回調、跨線程異步 | STL 算法、線程池任務、本地回調 |
「計算機科學中沒有什麼問題是加一層間接層解決不了的。」—— `__forwarding` 指針正是這句名言的教科書級實踐,通過雙重間接統一棧/堆訪問,優雅解決閉包變量生命週期難題。
理解 Block 底層機制,不僅能寫出正確的異步代碼,更能在面試中從容應對「閉包實現原理」這類高頻考點。