Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Resource Acquisition Is Initialization in C lang

Tags:

c

gcc

raii

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.

like image 971
Mynicks Avatar asked Feb 03 '17 13:02

Mynicks


2 Answers

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.

like image 66
cmaster - reinstate monica Avatar answered Sep 17 '22 10:09

cmaster - reinstate monica


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

like image 38
Jean-Baptiste Yunès Avatar answered Sep 18 '22 10:09

Jean-Baptiste Yunès