続・GCC拡張を使ったusing

以前、「GCC拡張を使ったusing」という記事を書きました。あれから2ヶ月半ほど経ちましたが、今になって思うと、肝心なGCC拡張を忘れていたことに気付きます。"cleanup"がそれです。

"cleanup"は__attribute__で関数を指定するGCC拡張です。"cleanup"を指定した自動変数は、その生存期間が終わる時点で、指定した関数が呼び出されます。つまり、デストラクタと同じことができるわけです。

話を簡単にするために、後始末としてはfreeでメモリを解放する場合に限定して考えることにします。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
void cleanup(void *p)
{
    free(*(void**)p);
    puts(__func__);
}
 

#define using(decl) \
    for (_Bool f = 1; f; f = 0) \
    for (__attribute__((cleanup(cleanup))) decl; f; f = 0)
 

int main(void)
{
    using(char *s = strcpy(malloc(4), "abc"))
    using(int *a = memcpy(malloc(10*sizeof(int)), (int[10]){1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 10*sizeof(int)))
    {
        puts(s);
        for (int i = 0; i < 10; i++)
            printf("%d\n", a[i]);
    }
    return 0;
}

cleanup関数が確実に予備ださていることを確認するために、putsで関数名を出力しています。実際に、このコードをコンパイルして(-std=gnu99オプションが必須です)、実行してみると、期待通りの動作になっています。

前回のものに比べれば、かなりシンタックスもすっきりしました。何より改善されたのは、gotoやreturnで脱出した場合でも、ちゃんとメモリが解放される点です。breakやcontinueはやや苦しいですが、まあ、しかたないでしょう。少なくとも、breakやcontinueで抜けた場合でもメモリは解放されますので、OKとしましょう。

最後にもう一工夫しないといけないのは、単にfreeでメモリを解放するだけではなく、ユーザー定義の解放関数を自動的に選択して呼び出すようにすることです。C#のusingでも、IDisposableから派生したクラスしか対応できないので、そう考えると、どこかに(オブジェクトの前にこっそり埋め込むのがよい?)解放関数を登録しておき、"cleanup"で指定するのは、その解放関数を呼び出すだけにしておくとよいのかもしれません。

この記事のトラックバックURL:

http://www.kijineko.co.jp/trackback/875