在纯 C 中实现 RAII? ?

有可能在纯 C 中实现 拉尔吗?

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

这纯粹是出于学术上的兴趣,我并不打算编写这种不可移植和疯狂的代码,但我想知道这是否完全可能。

19189 次浏览

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

也许最简单的方法是使用 goto 在函数结束时跳转到一个标签,但是对于您正在查看的这类东西来说,这可能太手工化了。

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

参见 给你

你看过配额吗?当 var 离开作用域时,它将自由。但是为了有效地使用它,调用者必须在将它发送给事物之前总是执行分配... ... 如果您正在实现 strup,那么,您就不能使用分配。

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

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


// ...
}

如果内存允许的话,gcc 在添加到 C99之前就已经支持这个特性了。这(大致)相当于简单的情况:

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

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

将 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)来实现上述功能。

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

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

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

当变量超出作用域时,clean up 属性运行一个函数

清理属性(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
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);
}