Implementing RAII in pure C?
Asked Answered
G

10

85

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.

Gnarled answered 15/12, 2008 at 13:40 Comment(6)
You can't simply overwrite the return address on the stack; you have to preserve the value on entry and then overwrite it with an alternative. Ugly, but possibly effective. Consider using arena-based memory allocation for memory. Otherwise, just be very careful (and worry about interrupts!).Precede
Is RAII that useful in the absence of exceptions? (just asking)Almund
@JoshPetitt sure, early return, and just not having to remember to free every single thing = fewer bugs.Norine
@JoshPetitt you at least have to write one less statement. eg fopen without corresponding fcloseRossierossing
I am surprised that no one suggested you use a C++ compiler, and write in that arcane C dialect that is compilable by C++ (just using RAII features when you want them). I am also surprised you have not accepted Johannes's answer, unless you are holding out for a "more general" solution.Inferential
Why stick to C and do weird things? Just start porting code to C++ if you can do it ;) Convince your bosses...Salami
B
116

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

Blastema answered 15/12, 2008 at 15:38 Comment(4)
This is way neater than I thought would be possible!Gnarled
For information, this feature is supported at least from GCC 4.0.0Gilburt
This also appears to work in clang 11.0.0 although it's not listed in the attribute reference.Graner
⁺¹. Note: you can initialize the variable on the same line where it's declared.Legit
B
15

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.

Benne answered 7/6, 2013 at 21:43 Comment(6)
Having to explicitly call a method (RaiiDestroyAll) kind of goes against the very idea of raii.Adebayo
This is the mechanism through the language. If you want you can hide the explicit call with macro such as, RTN_RAII(int, func_name, int, arg0, int, arg1, {/* code */}) (you can use P99 to do heavy lifting of macro).Benne
en.cppreference.com/w/cpp/language/raii "Another name for this technique is Scope-Bound Resource Management (SBRM), after the basic use case where the lifetime of an RAII object ends due to scope exit." Bjarne Stroustrup said "RAII is a bad name for the concept... A better name is probably: Constructor Acquires, Destructor Releases" The point is that release is automatic, no matter what. The point is that you shouldn't make a cleanup call. That's the definition of RAII. I've heard that some C compilers offer something similar as an extension, but C itself can't do it.Adebayo
Obviously C can't do automatic scoped based cleanup. From the question: "I assume it isn't possible in any sane way, but perhaps is it possible using some kind of dirty trick". What's I've provided is a mechanism as a workaround whereby you can get managed cleanup in C with the aid of a simple macro where you will not have to manually call destroy or the cleanup (cleanup will be done in the macro), but you will need to register the pointers.Benne
Even C++ requires you to mark the end of the scope somehow, usually with a close brace. It seems to me that RaiiDestroyAll() is merely serving the same purpose, except that it is uncoupled from the closing brace. One could re-couple them with a macro, but I don't think that would make things nicer: it is something of the essence of C++ and C to respectively hide and not hide implementation details, so this seems to me to be a fitting approach (and not a dirty trick) to RAII in C. And separate brace and RAII scope lets you do things like e.g. using one RAII scope over several brace ones.Terni
Follow-up to my last comment. A real-world example of what can be done when RAII-like memeory management is separated from the local stack-frame scope is the Linux kernel devm_kcalloc() and companion functions[1]. Might is make sense to mention this in the answer above? [1] mjmwired.net/kernel/Documentation/driver-model/devres.txtTerni
E
9

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.

Environmentalist answered 8/7, 2010 at 19:55 Comment(6)
Beware that 1) the stack is usually much more limited than the heap; 2) You basically cannot recover from a stack overflow (you will get a SIGSEGV, that you cannot handle). A failed malloc will return nullptr, while a failed new will throw std::bad_alloc.Salami
@RaúlSalinas-Monteagudo what do you mean by "much more limited" ?Graner
@jb: Depending on the OS, the size of the stack is often limited to only a few megabytes or so. But he's only sort of half right. Just for example, on Linux a failed malloc (or new) will frequently result in the OOMKILLER running, which may simply kill program in question (or may kill other programs to free up enough memory to let the allocation succeed). As such, although the heap is often larger, trying to use more than it has available can also be unrecoverable.Environmentalist
@jb: If I remember correctly, the stack size is (by default) 8 MB for a normal process and 2 MB for a thread. If you start creating arrays in the stack, you might well end up overflowing it. But that depends of course on the nature of your date and how deep you nest invocations. I would not go into such dangers for a reliable program.Salami
@RaúlSalinas-Monteagudo good points. 8 MB (or even 2 MB) is actually quite a lot of memory for most applications and I must admit I've never run into any trouble. However, I just wrote a simple test program and was able to crash it instantly by allocating a 10 MB array on the stack. Will definitely bear this in mind for future. Thanks!Graner
It's fine as long as you don't work with files, but once you do, 8 MB is most of the time too little.Howl
C
4

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.

Counterclaim answered 15/12, 2008 at 15:20 Comment(0)
S
1

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

Sibert answered 15/12, 2008 at 14:2 Comment(0)
B
1

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.

Barrett answered 24/4, 2009 at 7:2 Comment(2)
alloca() isn't really pure C. It's not in any C standard and as such is not available everywhere C is available. For instance it is not available in Microsoft's C compilers for Windows. See the C FAQ.Dilatation
It doesn't free when a variable leaves its scope. It frees when the function exits, which makes it very dangerous.Schliemann
O
1

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
Oujda answered 25/4, 2014 at 10:17 Comment(0)
C
0

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.

Cocaine answered 7/4, 2014 at 19:55 Comment(0)
B
0

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.

Babineaux answered 8/4, 2014 at 17:11 Comment(0)
E
0
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);
}
Erhart answered 28/4, 2019 at 16:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.