76

純粋な C でRAIIを実装することは可能ですか?

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

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

4

10 に答える 10

97

標準にはそのような可能性が含まれていないため、これは固有の実装依存です。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

こちらをご覧ください

于 2008-12-15T15:38:35.770 に答える
13

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

/* 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 などの高度なマクロ フレームワークを利用することをお勧めします。

于 2013-06-07T21:43:27.513 に答える
9

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

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

    // ...
}

メモリが機能する場合、gcc は C99 に追加されるずっと前にこの機能を持っていた/サポートしていました。これは、次の単純なケースと (ほぼ) 同等です。

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

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

于 2010-07-08T19:55:13.463 に答える
4

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

于 2008-12-15T15:20:30.593 に答える
1

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

于 2008-12-15T14:02:47.803 に答える
1

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

于 2009-04-24T07:02:16.663 に答える
0

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

于 2014-04-08T17:11:19.897 に答える
0

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

于 2014-04-07T19:55:41.563 に答える