Implementing RAII in pure C?

CRaii

C Problem Overview


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.

C Solutions


Solution 1 - C

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

Solution 2 - C

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.

Solution 3 - C

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.

Solution 4 - C

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.

Solution 5 - C

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".

Solution 6 - C

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.

Solution 7 - C

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

Solution 8 - C

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.

Solution 9 - 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.

Solution 10 - 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

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionelifinerView Question on Stackoverflow
Solution 1 - CJohannes Schaub - litbView Answer on Stackoverflow
Solution 2 - CKeldon AlleyneView Answer on Stackoverflow
Solution 3 - CJerry CoffinView Answer on Stackoverflow
Solution 4 - CJasper BekkersView Answer on Stackoverflow
Solution 5 - CRoger LipscombeView Answer on Stackoverflow
Solution 6 - CnelixView Answer on Stackoverflow
Solution 7 - Cuser184968View Answer on Stackoverflow
Solution 8 - Cuser3508333View Answer on Stackoverflow
Solution 9 - Cuser3510229View Answer on Stackoverflow
Solution 10 - Csmart masterView Answer on Stackoverflow