最近發現一個 clang 的 extension,可以提供 C 語言閉包功能 -- Blocks
(ref: https://en.wikipedia.org/wiki/Blocks_(C_language_extension))
由於是 extension,在編譯的時候要開 -fblocks -lBlocksRuntime
(libBlocksRuntime)
-fblocks 啟用 block pointer
-lBlocksRuntime 連結 <Block.h> 內的函式
它提供一種新的指標,指向一個 block 的位址,有點像 &&label 一樣紀錄 .text segment 的其中一個指令的 address
block pointer 的 declarator 是 '^',用法就像一般的 pointer 一樣,不過 block pointer 是不能 assign 給 non-function type variable 的
我們先做出一個 function type 'fun_t'
typedef void fun_t(void);
接著宣告一個 function type pointer variable
fun_t ^ptr;
然後再 assign 一個 block 給 ptr
ptr = ^(void) { printf("Hello""\n"); };
這個時候 ptr 就像是一個函式一樣,可以被呼叫
ptr();
output 會是
Hello
而這個 block 如果在函式裡,那麼它就在這個函式的 stack 上
假如在 global,那麼就在 .text 上,它的生命週期等同於其他相同 scope 的 auto variable
所以要是我這麼做
void (^functor(void))(void);
int main(void)
{
void (^func) = functor();
func();
return 0;
}
void (^functor(void))(void)
{
return ^(void) { /* some code here */ };
}
那麼等同於把一個 stack space 的物件傳出函式,這顯然不是什麼好事情吧...
比較直覺的作法是先把它 assign 給一個 static variable,然後再回傳
void (^functor(void))(void)
{
static void (^func)(void) = ^(void) { /* some code here */ };
return func;
}
但是這不也就代表著每個被 functor assign 的 function variables 都共享著同一個 static function literal 嗎?
這麼一來,要是這個 function literal 有使用到 functor 裡的 variable 的話,就會造成非預期的結果
void (^functor(void))(void);
int main(void)
{
void (^func1) = functor();
void (^func2) = functor();
func1();
func2();
return 0;
}
void (^functor(void))(void)
{
static int x = 5;
static void (^func)(void) = ^(void)
{
printf("%d\n", x);
x += x;
};
return func;
}
結果竟然會是 5 和 10!!!
這不是我們預期的閉包的行為,每個物件應該要各自獨立才對
有兩個函式,就專門處理這問題
Block_copy 負責把 Closure 的 environment copy 一份出來(到 heap?)
Block_release 負責把記憶體釋放還給系統
#include <stdio.h>
#include <Block.h>
void (^functor(void))(void);
int main(void)
{
void (^func1) = functor();
void (^func2) = functor();
func1();
func2();
Block_release(func1);
Block_release(func2);
return 0;
}
void (^functor(void))(void)
{
__block int x = 5;
return Block_copy(^(void)
{
printf("%d\n", x);
x += x;
});
}
這樣 fun1 和 fun2 的結果就會各自獨立互不干擾了~
到這裡,閉包的功能就都有了
來個閉包的實作範例好了
#include <stdio.h>
#include <Block.h>
typedef struct dog *dog;
struct dog
{
void (^bark)(void);
__attribute__((__unavailable__)) char dummy_padding[8 - (sizeof(void (^)(void)) % 8)];
};
struct dog *Dog(const char *);
int main(void)
{
dog marry = Dog("Merry");
dog peter = Dog("Peter");
marry->bark();
peter->bark();
Block_release(merry->bark);
Block_release(peter->bark);
free(merry);
free(peter);
return 0;
}
struct dog *Dog(const char *name)
{
struct dog *_dog = (struct dog *)malloc(sizeof(struct dog));
_dog->bark = Block_copy(^(void) { printf("%s\n", name); });
return _dog;
}
藉由閉包的特性,我們可以輕易的做出兩個行為不同的 bark 函式
一個印出 "Merry",一個印出 "Peter"