web-dev-qa-db-ja.com

純粋なCでRAIIを実装しますか?

[〜#〜] raii [〜#〜] を純粋なCで実装することは可能ですか?

私はそれが正気の方法で可能ではないと思います、しかし多分ある種の汚いトリックを使ってそれは可能です。標準のfree関数のオーバーロードが頭に浮かぶか、スタック上の戻りアドレスを上書きして、関数が戻るときに、何らかの方法でリソースを解放する他の関数を呼び出すようにしますか?または多分いくつかのsetjmp/longjmpトリックで?

これは純粋に学術的な関心事であり、私は実際にそのような移植性がなくクレイジーなコードを書くつもりはありませんが、それが可能かどうか疑問に思っています。

58
elifiner

標準にはそのような可能性が含まれていないため、これは本質的に実装に依存します。 GCCの場合、変数がスコープ外になると、cleanup属性が関数を実行します。

#include <stdio.h>

void scoped(int * pvariable) {
    printf("variable (%d) goes out of scope\n", *pvariable);
}

int main(void) {
    printf("before scope\n");
    {
        int watched __attribute__((cleanup (scoped)));
        watched = 42;
    }
    printf("after scope\n");
}

プリント:

before scope
variable (42) goes out of scope
after scope

ここ を参照してください

RAIIをCにするための1つの解決策(cleanup()がない場合)は、クリーンアップを実行するコードで関数呼び出しをラップすることです。これは、きちんとしたマクロ(最後に表示)にパッケージ化することもできます。

/* Publicly known method */
void SomeFunction() {
  /* Create raii object, which holds records of object pointers and a
     destruction method for that object (or null if not needed). */
  Raii raii;
  RaiiCreate(&raii);

  /* Call function implementation */
  SomeFunctionImpl(&raii);

  /* This method calls the destruction code for each object. */
  RaiiDestroyAll(&raii);
}

/* Hidden method that carries out implementation. */
void SomeFunctionImpl(Raii *raii) {
  MyStruct *object;
  MyStruct *eventually_destroyed_object;
  int *pretend_value;

  /* Create a MyStruct object, passing the destruction method for
     MyStruct objects. */
  object = RaiiAdd(raii, MyStructCreate(), MyStructDestroy);

  /* Create a MyStruct object (adding it to raii), which will later
     be removed before returning. */
  eventually_destroyed_object = RaiiAdd(raii,
      MyStructCreate(), MyStructDestroy);

  /* Create an int, passing a null destruction method. */
  pretend_value = RaiiAdd(raii, malloc(sizeof(int)), 0);

  /* ... implementation ... */

  /* Destroy object (calling destruction method). */
  RaiiDestroy(raii, eventually_destroyed_object);

  /* or ... */
  RaiiForgetAbout(raii, eventually_destroyed_object);
}

すべてのボイラープレートコードは、すべての呼び出しで同じになるため、マクロを使用してSomeFunctionで表すことができます。

例えば:

/* Declares Matrix * MatrixMultiply(Matrix * first, Matrix * second, Network * network) */
RTN_RAII(Matrix *, MatrixMultiply, Matrix *, first, Matrix *, second, Network *, network, {
  Processor *processor = RaiiAdd(raii, ProcessorCreate(), ProcessorDestroy);
  Matrix *result = MatrixCreate();
  processor->multiply(result, first, second);
  return processor;
});

void SomeOtherCode(...) {
  /* ... */
  Matrix * result = MatrixMultiply(first, second, network);
  /* ... */
}

注:上記のようなことを可能にするには、P99などの高度なマクロフレームワークを利用する必要があります。

10
Keldon Alleyne

コンパイラがC99(またはその大部分)をサポートしている場合は、次のような可変長配列(VLA)を使用できます。

int f(int x) { 
    int vla[x];

    // ...
}

メモリが機能する場合、gccはC99に追加されるかなり前にこの機能をサポートしていました。これは(おおよそ)次の単純な場合と同等です。

int f(int x) { 
    int *vla=malloc(sizeof(int) *x);
    /* ... */
    free vla;
}

ただし、ファイルのクローズ、データベース接続など、dtorが実行できる他のことは実行できません。

9
Jerry Coffin

おそらく最も簡単な方法は、gotoを使用して関数の最後にあるラベルにジャンプすることですが、それはおそらくあなたが見ているようなものには手動すぎます。

3
Jasper Bekkers

スタックのリターンアドレスを上書きすることを選択します。それは最も透明なものとしてうまくいくでしょう。 freeを置き換えると、ヒープに割り当てられた「オブジェクト」でのみ機能します。

1
Roger Lipscombe

Alloca()を見たことがありますか? varがスコープを離れると解放されます。しかし、それを効果的に使用するには、呼び出し元は常にアロカを実行してから送信する必要があります... strdupを実装している場合は、アロカを使用できません。

1
nelix

ねえ、あなたは再作成しようとしています CFront

0
Arkadiy

以前は属性のクリーンアップについて知りませんでした。確かに、それが適用できる優れたソリューションですが、setjmp/longjmpベースの例外実装ではうまく動作しないようです。クリーンアップ関数は、例外をスローしたスコープとそれをキャッチしたスコープの間の中間スコープ/関数に対しては呼び出されません。 Allocaにはこの問題はありませんが、allocaでは、メモリがスタックフレームから割り当てられるため、メモリチャンクの所有権をそれを呼び出した関数から外部スコープに譲渡することはできません。 {}の代わりにマクロブラケットを使用し、スコープの開始/終了に追加のロジックを関連付けることができるように戻る必要があると考えた場合、C++のunique_ptrおよびshared_ptrにいくぶん似たスマートポインターを実装することができます。実装については、 https://github.com/psevon/exceptions-and-raii-in-c のautocleanup.cを参照してください。

0
user3510229

一意の共有スマートポインターと例外のC実装については、 https://github.com/psevon/exceptions-and-raii-in-c を確認してください。この実装は、マクロブラケットBEGIN ... ENDに依存して、中括弧を置き換え、スコープ外になるスマートポインターを検出し、マクロをリターンに置き換えます。

0
user3508333
my implementation of raii for c in pure c and minimal asm
@ https://github.com/smartmaster/sml_clang_raii

**RAII for C language in pure C and ASM**

**featurs : **

-easy and graceful to use
- no need seperate free cleanup functions
- able to cleanup any resources or call any function on scope exits


**User guide : **

-add source files in src folder to your project
-include sml_raii_clang.h in.c file
-annote resource and its cleanup functions

/ *サンプルコード* /

void sml_raii_clang_test()
{
    //start a scope, the scope name can be any string
    SML_RAII_BLOCK_START(0);


    SML_RAII_VOLATILE(WCHAR*) resA000 = calloc(128, sizeof(WCHAR)); //allocate memory resource
    SML_RAII_START(0, resA000); //indicate starting a cleanup code fragment, here 'resA000' can be any string you want
    if (resA000) //cleanup code fragment
    {
        free(resA000);
        resA000 = NULL;
    }
    SML_RAII_END(0, resA000); //indicate end of a cleanup code fragment


    //another resource
    //////////////////////////////////////////////////////////////////////////
    SML_RAII_VOLATILE(WCHAR*) res8000 = calloc(128, sizeof(WCHAR));
    SML_RAII_START(0, D000);
    if (res8000)
    {
        free(res8000);
        res8000 = NULL;
    }
    SML_RAII_END(0, D000);


    //scope ended, will call all annoated cleanups
    SML_RAII_BLOCK_END(0);
    SML_RAII_LABEL(0, resA000); //if code is optimized, we have to put labels after SML_RAII_BLOCK_END
    SML_RAII_LABEL(0, D000);
}
0
smart master

ヨハネスの答えのこの部分を補完するために:

変数がスコープ外になると、クリーンアップ属性が関数を実行します

クリーンアップ属性には制限があります( http://gcc.gnu.org/onlinedocs/gcc-4.0.4/gcc/Variable-Attributes.html ):この属性は自動にのみ適用できます関数スコープ変数。

したがって、ファイルに静的変数がある場合は、次の方法で静的変数のRAIIを実装できます。

#include <stdio.h>
#include <stdlib.h>

static char* watched2;

__attribute__((constructor))
static void init_static_vars()
{
  printf("variable (%p) is initialazed, initial value (%p)\n", &watched2, watched2);
  watched2=malloc(1024);
}


__attribute__((destructor))
static void destroy_static_vars()
{
  printf("variable (%p), value( %p) goes out of scope\n", &watched2, watched2);
  free(watched2);
}

int main(void)
{
  printf("exit from main, variable (%p) value(%p) is static\n", &watched2, watched2);
  return 0;
}

これはテストです:

>./example
variable (0x600aa0) is initialazed, initial value ((nil))
exit from main, variable (0x600aa0) value(0x16df010) is static
variable (0x600aa0), value( 0x16df010) goes out of scope
0
Sergei Kurenkov