在纯C中实现RAII可能吗?
我认为以任何明智的方式都不可能,但也许可以使用某种肮脏的技巧。比如重载标准的free
函数,或者覆盖堆栈上的返回地址,使得函数返回时调用其他某个释放资源的函数?或者通过一些setjmp/longjmp技巧?
这只是纯粹的学术兴趣,我没有真正编写这种不可移植和疯狂的代码的意图,但我想知道是否有可能。
在纯C中实现RAII可能吗?
我认为以任何明智的方式都不可能,但也许可以使用某种肮脏的技巧。比如重载标准的free
函数,或者覆盖堆栈上的返回地址,使得函数返回时调用其他某个释放资源的函数?或者通过一些setjmp/longjmp技巧?
这只是纯粹的学术兴趣,我没有真正编写这种不可移植和疯狂的代码的意图,但我想知道是否有可能。
这是固有的实现依赖,因为标准没有包括这种可能性。对于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
请参见此处
在没有cleanup()
的情况下,将RAII引入C的一种解决方案是使用包装函数调用的代码进行清理。这也可以封装在一个整洁的宏中(在末尾显示)。
/* 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)来使上述操作成为可能。RaiiDestroyAll
)有点违背了 RAII 的核心思想。 - Mooing DuckRTN_RAII(int, func_name, int, arg0, int, arg1, {/* code */})
(你可以使用 P99 来完成宏的重活)。 - Keldon Alleyneint f(int x) {
int vla[x];
// ...
}
如果我的记忆没有出错,gcc在C99标准之前就已经有/支持了这个功能。这大致相当于以下简单情况:
int f(int x) {
int *vla=malloc(sizeof(int) *x);
/* ... */
free vla;
}
然而,它不允许你做dtor可以做的其他事情,比如关闭文件、数据库连接等。
可能最简单的方法是使用goto跳转到函数末尾的标签,但这对于你想要的那种事情来说可能太繁琐了。
我会选择覆盖栈上的返回地址。这是最透明的方法。替换free
只适用于堆分配的“对象”。
alloca()
并非纯粹的C语言,它没有被包含在任何C标准中,因此在C可用的所有地方都不可用。例如,在Windows上,Microsoft的C编译器就不支持它。请参阅C FAQ。 - hippietrail#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
请查看https://github.com/psevon/exceptions-and-raii-in-c,了解C语言实现的独占和共享智能指针以及异常处理。该实现依赖于宏括号BEGIN ... END替换大括号,并检测智能指针是否超出作用域,同时使用宏替换return语句。
我之前不知道属性清理。当适用时,这确实是一个很好的解决方案,但它似乎与基于setjmp/longjmp的异常实现不兼容;清理函数不会为抛出异常的作用域和捕获它的作用域之间的任何中间作用域/函数调用。Alloca没有这个问题,但使用alloca分配的内存块无法从调用它的函数转移到外部作用域,因为内存是从堆栈帧中分配的。可以实现类似于C++ unique_ptr和shared_ptr的智能指针,但需要使用宏括号而不是{}和return,以便能够将额外的逻辑关联到作用域进入/退出。请参见https://github.com/psevon/exceptions-and-raii-in-c中的autocleanup.c实现。
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);
}