Blocks拡張を弄ってみる

実は全然弄ってなかったんだけど、GCDがオープンソース化するとか言うので、ちょっと弄ってみる気になる。スレッド系はこれで Win32 Thread -> pthread -> TBB -> (GCD?) という学習pathになる、、、のかな。
とりあえずその前に必要なblocks拡張を調べてみる。

クロージャと言われると、、、

クロージャが使える、といわれるとついつい反射的に作ってしまうのがカウンタ、なんだけど実際作ってみようかなとか思って、しかしふと、Cの実装でスタックフレーム中にある環境をどうやって保持すんのじゃ、とか凄く不思議になる。ふうむ不思議だ。とりあえず何も考えずに書いてみる。

#include <stdio.h>

typedef int (^Acc)(int);

Acc makeAcc(int start)
{
    return  ^(int add) { start += add; return start;};
}

main()
{
    int i;
    Acc a = makeAcc(10);
    Acc b = makeAcc(20);

    for (i = 0; i< 10; i++)
        printf("%d, %d\n", a(10), b(5));
}

こんな感じかなー。とか思っておもむろにコンパイルしてみると

acc.c: In function ‘__makeAcc_block_invoke_1’:
acc.c:7: error: assignment of read-only variable ‘start’
acc.c: In function ‘makeAcc’:
acc.c:7: error: returning block that lives on the local stack

はあ、なるほど。やっぱりなあ。というか最初のエラーはなんぞや。デフォルトではimmutableで最適化するのかな。
はて、どうするかな。

環境をヒープにコピーする

/usr/include をあさってみると、そのまんま Block.hとかいうのがあった。これを見るとありましたありました。そうです、君です。

// Create a heap based copy of a Block or simply add a reference to an existing one.
// This must be paired with Block_release to recover memory, even when running
// under Objective-C Garbage Collection.
BLOCK_EXPORT void *_Block_copy(const void *aBlock);

// Lose the reference, and if heap based and last reference, recover the memory
BLOCK_EXPORT void _Block_release(const void *aBlock);

という訳でどうやら環境をヒープにコピーして永続化(?)するらしい。GC無いので当然ながら手動で解放する必要あり。なんか非常に恐ろしいなあ。凄いメモリ食ったりして。スタックフレームをまんまコピるのかな。
という訳でとりあえず足してみる。

#include <stdio.h>
#include <Block.h>

typedef int (^Acc)(int);

Acc makeAcc(int start)
{
    Acc a = Block_copy(^(int add) { start += add; return start;});
    return a;
}

main()
{
    int i;
    Acc a = makeAcc(10);
    Acc b = makeAcc(20);

    for (i = 0; i< 10; i++)
        printf("%d, %d\n", a(10), b(5));

    Block_release(a);
    Block_release(b);
}

おお、確かに一つエラーが減った。

記憶クラス、というか qualifier

immutableのほうはどうしたもんかと思ってLLVMをページを見てみた。
http://clang.llvm.org/docs/BlockLanguageSpec.txt
あった。

The __block Storage Qualifier

In addition to the new Block type we also introduce a new storage qualifier, __block, for local variables. [testme: a __block declaration within a block literal]  The __block storage qualifier is mutually exclusive to the existing local storage qualifiers auto, register, and static.[testme]  Variables qualified by __block act as if they were in allocated storage and this storage is automatically recovered after last use of said variable.  An implementation may choose an optimization where the storage is initially automatic and only "moved" to allocated (heap) storage upon a Block_copy of a referencing Block.  Such variables may be mutated as normal variables are.

なあるほど。確かに確保したら誰かが解放しなけりゃいけないということすね。
付けてみる。

#include <stdio.h>
#include <Block.h>

typedef int (^Acc)(int);

Acc makeAcc(int start)
{
    __block int val = start;
    Acc a = Block_copy(^(int add) { val += add; return val;});
    return a;
}

main()
{
    int i;
    Acc a = makeAcc(10);
    Acc b = makeAcc(20);

    for (i = 0; i< 10; i++)
        printf("%d, %d\n", a(10), b(5));

    Block_release(a);
    Block_release(b);
}

記憶クラスなので引数に直接は付けられないみたい。

sardine% ./accumulate 
20, 25
30, 30
40, 35
50, 40
60, 45
70, 50
80, 55
90, 60
100, 65
110, 70

おっけ。

次は

とりあえずメモリ使用量とか、仕組みとか調べてみよう。その後本題