Resource Acquisition Is Initialization in C lang
Asked Answered
R

3

6

The question is: Could you please help me understand better the RAII macro in C language(not c++) using only the resources i supply at the bottom of this question? I am trying to analyse it in my mind so as to understand what it says and how it makes sense(it does not make sense in my mind). The syntax is hard. The focus of the question is: i have trouble reading and understanding the weird syntax and its implementation in C language. For instance i can easily read, understand and analyse(it makes sense to me) the following swap macro:

#define myswap(type,A,B) {type _z; _z = (A); (A) = (B); (B) = _z;} 

(the following passage is lifted from the book: Understanding C pointers)

In C language the GNU compiler provides a nonstandard extension to support RAII.

The GNU extension uses a macro called RAII_VARIABLE. It declares a variable and associates with the variable:

  • A type
  • A function to execute when the variable is created
  • A function to execute when the variable goes out of scope

    The macro is shown below:

    #define RAII_VARIABLE(vartype,varname,initval,dtor) \
    void _dtor_ ## varname (vartype * v) { dtor(*v); } \
    vartype varname __attribute__((cleanup(_dtor_ ## varname))) = (initval)
    

    Example:

    void raiiExample() {
    RAII_VARIABLE(char*, name, (char*)malloc(32), free);
    strcpy(name,"RAII Example");
    printf("%s\n",name);
    } 
    
    int main(void){
        raiiExample();
    }
    

When this function is executed, the string “RAII_Example” will be displayed. Similar results can be achieved without using the GNU extension.

Refractive answered 3/2, 2017 at 13:30 Comment(9)
I don't like this method, this don't look like C.Groundsheet
Why use this macro instead of... using C++?Pressmark
The focus of the question is: Could you please help me understand better the RAII macro in C language? I am struggling with the syntax and how it is implemented in Clang. I know what it does(allocates and deallocates memory.....). :)Refractive
@Refractive The macro doesn't "allocate and deallocate memory". That's what the particular single use of the macro that you shown does, but the macro does what the documentation says it does: it associates stuff with the variable, and keeps track of when the variable goes out of scope.Haphtarah
I do not know how to write the question more transparently. Let me try this: i have trouble reading and understanding the weird syntax and its implementation. I gave an example of the swap syntax(that one i get it, but it is irrelevant to RAII). Thanks.Refractive
You probably don't want this anyway. :-) Hardcore C buffs dislike C++ because the compiler adds function calls - like constructors and destructors - that don't show up in the source code. Trying to hack this back into C seems really, really odd. If this is what you want, just use a C++ compiler and std::string will do what you example does, and without any non-portable extensions.Blus
There is a fundamental problem with this question: although it's related to C, it's about a GNU C extension, not really about C itself. I have therefore added a [gcc] tag, but you should consider whether you want to spend any time at all on learning a GNU-specific extension.Mesocarp
Note that when analyzing a macro, whether your own or someone else's, it can be useful to run the preprocessor on an invocation of it to see how it expands. With gcc, you can run the preprocessor without going on to compile the result by giving the -E option; for example, gcc -E my_source.c.Mesocarp
Minor point (char*) cast is not needed in RAII_VARIABLE(char*, name, (char*)malloc(32), free); Removing it reduces code noise.Meshach
R
4

Of course you can achieve anything without using RAII. RAII use case it to not have to think about releasing ressources explicitly. A pattern like:

void f() {
    char *v = malloc(...);
    // use v
    free v;
}

need you to take care about releasing memory, if not you would have a memory leak. As it is not always easy to release ressources correctly, RAII provides you a way automatize the freeing:

void f() {
    RAII_VARIABLE(char*, v, malloc(...), free);
    // use v
}

What is interesting is that ressource will be released whatever the path of execution will be. So if your code is a kind of spaghetti code, full of complex conditions and tests, etc, RAII lets you free your mind about releasing...

Rodger answered 3/2, 2017 at 13:39 Comment(0)
B
4

Ok, let's look at the parts of the macro line by line

#define RAII_VARIABLE(vartype,varname,initval,dtor) \

This first line is, of course, the macro name plus its argument list. Nothing unexpected here, we seem to pass a type, a token name, some expression to init a variable, and some destructor that will hopefully get called in the end. So far, so easy.

void _dtor_ ## varname (vartype * v) { dtor(*v); } \

The second line declares a function. It takes the provided token varname and prepends it with the prefix _dtor_ (the ## operator instructs the preprocessor to fuse the two tokens together into a single token). This function takes a pointer to vartype as an argument, and calls the provided destructor with that argument.

This syntax may be unexpected here (like the use of the ## operator, or the fact that it relies on the ability to declare nested functions), but it's no real magic yet. The magic appears on the third line:

vartype varname __attribute__((cleanup(_dtor_ ## varname))) = (initval)

Here the variable is declared, without the __attribute__() this looks pretty straight-forward: vartype varname = (initvar). The magic is the __attribute__((cleanup(_dtor_ ## varname))) directive. It instructs the compiler to ensure that the provided function is called when the variable falls out of scope.


The __attribute__() syntax is is a language extension provided by the compiler, so you are deep into implementation defined behavior here. You cannot rely on other compilers providing the same __attribute__((cleanup())). Many may provide it, but none has to. Some older compilers may not even know the __attribute__() syntax at all, in which case the standard procedure is to #define __attribute__() empty, stripping all __attribute__() declarations from the code. You don't want that to happen with RAII variables. So, if you rely on an __attribute__(), know that you've lost the ability to compile with any standard conforming compiler.

Bowerbird answered 3/2, 2017 at 14:26 Comment(0)
S
3

The syntax is little bit tricky, because __attribute__ ((cleanup)) expects to pass a function that takes pointer to variable. From GCC documentation (emphasis mine):

The function must take one parameter, a pointer to a type compatible with the variable. The return value of the function (if any) is ignored.

Consider following incorrect example:

char *name __attribute__((cleanup(free))) = malloc(32);

It would be much simpler to implement it like that, however in this case free function implicitely takes pointer to name, where its type is char **. You need some way to force passing the proper object, which is the very idea of the RAII_VARIABLE function-like macro.

The simplified and non-generic incarnation of the RAII_VARIABLE would be to define function, say raii_free:

#include <stdlib.h>

void raii_free(char **var) { free(*var); }

int main(void)
{
    char *name __attribute__((cleanup(raii_free))) = malloc(32);
    return 0;
}
Silicic answered 3/2, 2017 at 14:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.