在纯C中实现RAII?
纯粹的C可以实现RAII吗?
我认为这是不可能的,但也许有可能使用某种肮脏的伎俩。 重载标准的free
函数会想到或者覆盖堆栈上的返回地址,这样当函数返回时,它会调用某个其他函数以某种方式释放资源? 或者,也许有一些setjmp / longjmp技巧?
这是一个纯粹的学术兴趣,我不打算写这样的不可移植和疯狂的代码,但我想知道这是否是可能的。
这是内在的实现依赖,因为标准不包括这种可能性。 对于GCC,当variables超出作用域时, 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
看到这里
如果您的编译器支持C99(甚至是其中的大部分),则可以使用可变长度数组(VLA),例如:
int f(int x) { int vla[x]; // ... }
如果使用内存,gcc在添加到C99之前已经支持这个function。 这(大致)相当于以下简单情况:
int f(int x) { int *vla=malloc(sizeof(int) *x); /* ... */ free vla; }
但是,它不会让你做任何其他的事情,如closures文件,数据库连接等。
将RAII引入C的一种解决scheme(当您没有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, eventually_destroyed_object; int *pretend_value; /* Create a MyStruct object, passing the destruction method for MyStruct objects. */ object = RaiiAdd(raii, malloc(sizeof(MyStruct)), MyStructDestroy); /* Create a MyStruct object (adding it to raii), which will later be removed before returning. */ eventually_destroyed_object = RaiiAdd(raii, malloc(sizeof(MyStruct)), 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
中用macros来expression所有的锅炉板代码,因为每个调用都是一样的。
也许最简单的方法就是使用goto跳转到一个函数末尾的标签上,但是对于你所看到的东西来说,这可能太过于手工了。
我会select覆盖堆栈中的返回地址。 它会以最透明的方式工作。 free
replace只能使用堆分配的“对象”。
你看过alloca()吗? 当var离开作用域时它将被释放。 但要有效地使用它,调用者必须在将它发送给事物之前始终执行alloca …如果您正在执行strdup,那么您不能使用alloca。
嘿,你正试图重buildCFront !
查看https://github.com/psevon/exceptions-and-raii-in-c ,了解独特和共享智能指向和exception的C实现。 这个实现依赖于macros括号BEGIN … ENDreplace大括号,并检测超出范围的智能指针,以及macros的replace返回。
之前我不知道属性清理。 当然,这是一个很好的解决scheme,但是对于基于setjmp / longjmp的exception实现来说,这似乎不太合适。 在抛出exception的范围和捕获范围之间的任何中间范围/函数都不会调用清除函数。 Alloca没有这个问题,但是使用alloca,你不能将内存块的所有权从调用它的函数转移到外部范围,因为内存是从堆栈帧中分配的。 有可能实现类似于C ++ unique_ptr和shared_ptr的智能指针,认为它需要使用macros括号而不是{},并返回能够将额外的逻辑关联到作用域的入口/出口。 请参阅https://github.com/psevon/exceptions-and-raii-in-c中的autocleanup.c获取实现。;
为了补充Johannes的答案:
当variables超出作用域时,cleanup属性将运行一个函数
清理属性有一个限制( http://gcc.gnu.org/onlinedocs/gcc-4.0.4/gcc/Variable-Attributes.html ):该属性只能应用于自动function范围variables。
所以如果文件中有一个静态variables,就可以通过这种方式实现一个静态variables的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; }
这是一个testing:
>./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