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 的一种解决方案(当您没有 RAII 时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 等高级宏框架来实现上述操作。

于 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 回答
1

为了补充约翰内斯回答的这一部分:

当变量超出范围时,cleanup 属性运行一个函数

清理属性有一个限制(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
于 2014-04-25T10:17:26.070 回答
0

检查https://github.com/psevon/exceptions-and-raii-in-c以了解唯一和共享智能指针和异常的 C 实现。此实现依赖于宏括号 BEGIN ... END 替换大括号并检测超出范围的智能指针,以及返回的宏替换。

于 2014-04-07T19:55:41.563 回答
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
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);
}
于 2019-04-28T16:35:59.567 回答