用纯 C 实现 RAII?

发布于 2024-07-10 20:48:13 字数 318 浏览 6 评论 0原文

是否可以在纯 C 中实现 RAII

我认为以任何理智的方式都是不可能的,但也许使用某种肮脏的技巧是可能的。 我想到的是重载标准的 free 函数,或者可能覆盖堆栈上的返回地址,以便当函数返回时,它会调用其他以某种方式释放资源的函数? 或者也许有一些 setjmp/longjmp 技巧?

这纯粹是出于学术兴趣,我无意实际编写如此不可移植且疯狂的代码,但我想知道这是否可能。

Is it possible to implement RAII in pure C?

I assume it isn't possible in any sane way, but perhaps is it possible using some kind of dirty trick. Overloading the standard free function comes to mind or perhaps overwriting the return address on the stack so that when the function returns, it calls some other function that somehow releases resources? Or maybe with some setjmp/longjmp trick?

This is of a purely academic interest and I have no intention of actually writing such unportable and crazy code but I'm wondering if that is at all possible.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

奢华的一滴泪 2024-07-17 20:48:13

这是固有的依赖于实现的,因为标准不包括这种可能性。 对于 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

请参阅 此处

This is inherent implementation dependent, since the Standard doesn't include such a possibility. For GCC, the cleanup attribute runs a function when a variable goes out of scope:

#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");
}

Prints:

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

See here

等风来 2024-07-17 20:48:13

将 RAII 引入 C 的一种解决方案(当您没有 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)来使上述内容成为可能。

One solution to bring RAII to C (when you don't have cleanup()) is to wrap your function call with code that will perform a cleanup. This can also be packaged in a tidy macro (shown at the end).

/* 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);
}

You can express all of the boiler plate code in SomeFunction with macros since it will be the same for every call.

For example:

/* 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);
  /* ... */
}

Note: you'd want to make use of an advanced macro framework such as P99 to make something like the above possible.

后来的我们 2024-07-17 20:48:13

如果您的编译器支持 C99(甚至是其中的很大一部分),您可以使用可变长度数组 (VLA),例如:

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

    // ...
}

如果没记错的话,gcc 在添加到 C99 之前就已经/支持此功能了。 这(大致)相当于以下简单情况:

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

但是,它不允许您执行 dtor 可以执行的任何其他操作,例如关闭文件、数据库连接等。

If your compiler supports C99 (or even a substantial part of it) you can use a variable length array (VLA), such as:

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

    // ...
}

If memory serves, gcc had/supported this feature well before it was added to C99. This is (roughly) equivalent to the simple case of:

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

It does not, however, let you do any of the other things a dtor can do such as closing files, database connections, etc.

内心激荡 2024-07-17 20:48:13

可能最简单的方法是使用 goto 跳转到函数末尾的标签,但这对于您正在查看的类型来说可能过于手动。

Probably the easiest way is to use goto to jump to a label at the end of a function but that's probably too manual for the sort of thing you're looking at.

紙鸢 2024-07-17 20:48:13

我会选择覆盖堆栈上的返回地址。 它会成为最透明的。 替换free仅适用于堆分配的“对象”。

I'd opt for overwriting the return address on the stack. It'd work out as the most transparent. Replacing free will only work with heap-allocated "objects".

等风也等你 2024-07-17 20:48:13

你看过 alloca() 吗? 当 var 离开作用域时它将释放。 但为了有效地使用它,调用者必须始终在将其发送到事物之前执行分配...如果您正在实现 strdup,那么,您不能使用分配。

Have you looked at alloca()? It will free when an var leaves scope. But to use it effecticly the caller must always do the alloca before sending it to things... If you were implementing strdup, well, you can't use alloca.

这个俗人 2024-07-17 20:48:13

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

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

cleanup 属性有一个限制 (http://gcc.gnu.org/onlinedocs/gcc-4.0.4/gcc/Variable-Attributes.html):此属性只能应用于 auto 函数作用域变量。

因此,如果文件中有静态变量,则可以通过以下方式为静态变量实现 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

To complement this part of Johannes's answer:

the cleanup attribute runs a function when a variable goes out of scope

There is a limitation on cleanup attribute (http://gcc.gnu.org/onlinedocs/gcc-4.0.4/gcc/Variable-Attributes.html): This attribute can only be applied to auto function scope variables.

So if there is a static variable in a file it is possible to implement RAII for a static variable in this way:

#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;
}

This is a test:

>./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
筑梦 2024-07-17 20:48:13

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

Check https://github.com/psevon/exceptions-and-raii-in-c for a C implementation of unique and shared smartpointers and exceptions. This implementation relies on macro brackets BEGIN ... END replacing braces and detecting smartpointers going out of scope, as well as macro replacement for return.

燃情 2024-07-17 20:48:13

我之前不知道属性清理。 当然,这是一个适用的简洁解决方案,但它似乎在基于 setjmp/longjmp 的异常实现中表现不佳; 抛出异常的作用域和捕获异常的作用域之间的任何中间作用域/函数都不会调用清理函数。 Alloca 没有这个问题,但使用 alloca 时,您无法将内存块的所有权从调用它的函数转移到外部作用域,因为内存是从堆栈帧分配的。 可以实现有点类似于 C++ unique_ptr 和共享_ptr 的智能指针,认为它需要使用宏括号而不是 {} 并返回,以便能够将额外的逻辑与作用域进入/退出关联起来。 请参阅 https://github.com/psevon/exceptions-and- 中的 autocleanup.c raii-in-c 的实现。

I didn't know about attribute cleanup before. Certainly a neat solution where it's applicable, but it doesn't seem to behave well with setjmp/longjmp based exception implementations; the cleanup function is not called for any intermediate scopes/functions between the scope that threw the exception and the scope that catches it. Alloca doesn't have this problem, but with alloca you cannot transfer ownership of the memory chunk to an outer scope from the function that called it since the memory is allocated from the stack frame. It's possible to implement smartpointers somewhat akin to C++ unique_ptr and shared_ptr, thought it requires using macro brackets instead of {} and return to be able to associate extra logic to scope entry/exit. See autocleanup.c in https://github.com/psevon/exceptions-and-raii-in-c for an implementation.

相对绾红妆 2024-07-17 20:48:13
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);
}
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

/*
sample code
*/

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);
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文