32

Unfortunately, in C there aren't any smart pointers.. but is it possible to build a macro that wrap variable declaration and invoke function call with that variable as an input variable upon leaving the scope where the variable was declared ?

Sorry for the long phrase, but I'm working on xnu kernel where you have many elements that have built-in reference counters, and one must not forget to unref this element when done using it to avoid memory leaks.

For example, if I have the following type of proc_t:

struct proc;
typedef struct proc * proc_t;

I want to declare a stack variable based on this type within a scope, for example :

{
    proc_t_release_upon_exit proc_t proc_iter = proc_find(mypid);
    //the rest of the code in this scope 
}

After preprocessor analyze the macro and before compilation, the following code I expect to be generated is :

{ 
    proc_t myproc = proc_find(mypid)
    //the rest of the code in scope
    proc_rele(myproc);
}

Is there any way to define such macro as in C ?

msc
  • 33,420
  • 29
  • 119
  • 214
Irad K
  • 867
  • 6
  • 20
  • 1
    That is not posible because you want to have code at two places (the entry and exit) and a macro can only be at one place. – Paul Ogilvie Jun 29 '17 at 08:02
  • 4
    The method of work with C is different. There is no need to invent such macros. You have the full control and for that reason you can make fast software - not to guess what it is doing now and why is slow. – i486 Jun 29 '17 at 08:05
  • 5
    You could easily argue whether it's *unfortunate* that C has no smart pointers - I think it's fortunate. – tofro Jun 29 '17 at 08:13
  • 10
    The main difference between C and C++ is actually that C++ got "RAII" (automatic call of destructors). Most other C++ features such as encapsulation or polymorphism can be implemented in C, but not RAII. – Lundin Jun 29 '17 at 08:57
  • related: https://stackoverflow.com/q/34574933/1366431 – Alex Celeste Jun 29 '17 at 12:42
  • 5
    If you need this you need to not use C. – Ben Jun 29 '17 at 19:33

3 Answers3

39

You could use the cleanup variable attribute in GCC. Please take a look at this: http://echorand.me/site/notes/articles/c_cleanup/cleanup_attribute_c.html

Sample code:

#include <stdio.h>
#include <stdlib.h>

void free_memory(void **ptr)
{
    printf("Free memory: %p\n", *ptr);
    free(*ptr);
}

int main(void)
{
    // Define variable and allocate 1 byte, the memory will be free at
    // the end of the scope by the free_memory function. The free_memory 
    // function will get the pointer to the variable *ptr (double pointer
    // **ptr).
    void *ptr  __attribute__ ((__cleanup__(free_memory))) = malloc(1);
    return 0;
}

If you save the source code in a file named main.c, you could compile it with this command:

gcc main.c -o main

and verify if there are any memory leaks by:

valgrind ./main

Example output from valgrind:

==1026== Memcheck, a memory error detector
==1026== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==1026== Using Valgrind-3.10.1 and LibVEX; rerun with -h for copyright info
==1026== Command: ./main
==1026== 
Free memory: 0x51ff040
==1026== 
==1026== HEAP SUMMARY:
==1026==     in use at exit: 0 bytes in 0 blocks
==1026==   total heap usage: 1 allocs, 1 frees, 1 bytes allocated
==1026== 
==1026== All heap blocks were freed -- no leaks are possible
==1026== 
==1026== For counts of detected and suppressed errors, rerun with: -v
==1026== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Tamás Sengel
  • 55,884
  • 29
  • 169
  • 223
  • 8
    I guess this is a workable solution if you can tie yourself to proprietary, compiler-specific extensions. But then one does have to wonder, why not just compile your C code as C++ to benefit from the RAII built into the language? Then you're not relying on anything non-portable, and the results are even better. (I know, I know, C++ isn't a *strict* superset of C, but it's darn close, and minimal to no modifications would be required in a typical C code base.) – Cody Gray - on strike Jun 29 '17 at 12:06
  • 7
    cleanup attribute is supported in clang as well – BurnsBA Jun 29 '17 at 12:15
  • 5
    @CodyGray not every valid C code has any semantics in C++, even if it compiles. E.g. much of C code uses unions for type punning, but this is UB in C++. Although GCC does define it the same way as in C, switching to C++ doesn't make your code more portable in this case. – Ruslan Jun 29 '17 at 13:54
  • @cat if you're a clang fanatic then yes? – user253751 Jun 29 '17 at 22:45
  • @immibis what? clang isn't proprietarry either – cat Jun 29 '17 at 23:12
  • @cat If you're using clang then GCC-specific extensions might as well be proprietary. If you're using GCC then clang-specific extensions might as well be proprietary. The opensourcedness of the extension isn't what matters, what matters is whether it's supported by the compiler you're trying to use. – user253751 Jun 30 '17 at 00:21
  • 1
    @immibis "proprietary" implies it is legally and literally impossible to reimplement equivalent behaviour elsewhere; people can and do write extensions for clang that allow GNU C to compile in it – cat Jun 30 '17 at 00:40
  • @cat Since when does "proprietary" imply that? By that definition, most commercial software is not proprietary. (Take Internet Explorer for example, since Firefox and Chromium implement equivalent behaviour) – user253751 Jun 30 '17 at 00:45
  • 4
    There are multiple definitions for "proprietary"; in particular, this one: "nonstandard and used only by one particular organization, as in *a proprietary extension to a standard*". I didn't innovate this usage; "proprietary" is used this way with respect to [CSS extensions](https://developer.mozilla.org/en-US/docs/Web/CSS/List_of_Proprietary_CSS_Features) as well, even though Mozilla is open-source just like GCC. The point is that this behavior is *not* part of the C language standard, thus non-portable, with the effect of locking you into GCC in a *practical* sense, not a legal one. @cat – Cody Gray - on strike Jun 30 '17 at 09:54
  • @cat So by that definition, it's impossible to reimplement a proprietary extension because once you do that it's no longer proprietary. Totally vacuous. – user253751 Jul 02 '17 at 23:18
  • @immibis: The discussion about ‘proprietary’ is irrelevant, ‘compiler-specific’ is more the point: one must ask oneself if one is prepared to tie oneself to GCC, clang or whatever; if so, no problem. – PJTraill Jul 04 '17 at 19:55
  • @PJTraill and "compiler-specific" is **exactly** what CodyGray meant by "proprietary". – user253751 Jul 04 '17 at 22:50
18

C does provide a way to place code syntactically before other code that will execute first: the for block. Remember that clause 3 of a for structure can contain an arbitrary expression, and always runs after the execution of the main block.

You can therefore create a macro that makes a predetermined call after a given chunk of following code by wrapping a for block in a macro:

#define M_GEN_DONE_FLAG() _done_ ## __LINE__ 

#define M_AROUND_BLOCK2(FLAG, DECL, BEFORE, AFTER) \
  for (int FLAG = (BEFORE, 0); !FLAG; ) \
    for (DECL; !FLAG; FLAG = (AFTER, 1))

#define M_AROUND_BLOCK(DECL, BEFORE, AFTER) M_AROUND_BLOCK2(M_GEN_DONE_FLAG(), DECL, BEFORE, AFTER)

#define M_CLEANUP_VAR(DECL, CLEANUP_CALL) M_AROUND_BLOCK(DECL, (void)0, CLEANUP_CALL)

...and you can use it like this:

#include <stdio.h>

struct proc;
typedef struct proc * proc_t;

proc_t proc_find(int);
void proc_rele(proc_t);

void fun(int mypid) {
  M_CLEANUP_VAR (proc_t myproc = proc_find(mypid), proc_rele(myproc))
  {
    printf("%p\n", &myproc); // just to prove it's in scope
  }
}

The trick here is that a for block accepts a following statement, but if we don't actually put that statement in the macro definition, we can follow the macro invocation with a normal code block and it will "magically" belong to our new scoped-control-structure syntax, simply by virtue of following the expanded for.

Any optimizer worth using will remove the loop flag at its lowest optimization settings. Note that name clashing with the flag is not a huge concern (i.e. you don't really need a gensym for this) because the flag is scoped to the loop body, and any nested loops will safely hide it if they use the same flag name.

The bonus here is that the scope of the variable to cleanup is restricted (it cannot be used outside of the compound immediately following its declaration) and visually explicit (because of said compound).

Pros:

  • this is standard C with no extensions
  • the control flow is straightforward
  • it's actually (somehow) less verbose than __attribute__ __cleanup__

Cons:

  • it doesn't provide "full" RAII (i.e. won't protect against goto or C++ exceptions: __cleanup__ is usually implemented with C++ machinery under the hood so it's more complete). More seriously, it doesn't protect against early return (thanks @Voo). (You can at least guard against a misplaced break - if you want to - by adding a third line, switch (0) default: to the end of M_AROUND_BLOCK2.)
  • not everyone agrees with syntax-extending macros (but consider that you are extending C's semantics here, so...)
Alex Celeste
  • 12,824
  • 10
  • 46
  • 89
  • 10
    Wow. As with most things like this, I don't know whether to be amazed or horrified, or both. What does this look like for your example after the pre-processor runs? – Baldrickk Jun 29 '17 at 13:59
  • 15
    Apart from not protecting against such obscure things as goto or C++ exceptions it also doesn't protect against a simple `return` which seems more likely to cause troubles. – Voo Jun 29 '17 at 14:07
  • I'm wondering if it could be done with one `for` loop, something along the lines of: `for(init_code, flag=1; flag--; cleanup){/*your code here*/}` – Baldrickk Jun 29 '17 at 14:22
  • 3
    @Voo which is where my preference would be to split the memory management and the functional components. The called function sets up the memory, then calls the functional component, when _that_ returns (for whatever reason) the cleanup is done automatically. – Baldrickk Jun 29 '17 at 14:23
  • 1
    If you can write `something() { ... }` then why can't you write `something() ... something_else()`? I fail to see the difference. If you are allowed to type stuff at the end of the function, you might as well type `foo()` as `}`, which will also solve the whole issue. – Lundin Jun 29 '17 at 14:41
  • ah, can't really do it in one for because of https://stackoverflow.com/questions/3348766/how-to-initialize-several-variables-in-a-for-loop-in-c although you could define it as `#define clean_up(DECL, CLEAN_FN) int flag__LINE__=1; for(DECL; flag__LINE__--; CLEAN_FN)` or `#define clean_up(DECL, CLEAN_FN) DECL; for(int flag__LINE__=1; flag__LINE__--; CLEAN_FN)` at the cost of having either the flag or the declared type pointer left in local scope. – Baldrickk Jun 29 '17 at 14:51
  • @Lundin I guess there isn't anything to stop you from writing code after this, but in any case, it would only be the very next line or block that the variable would be initialised for. Or am I misunderstanding you? – Baldrickk Jun 29 '17 at 15:10
  • @Lundin: You have `something` of a point, but I suspect the intention is to ensure that ‘construction’ and ‘destruction’ are specified together, so that it is clearer that if one is changed the other needs to be. – PJTraill Jul 04 '17 at 19:44
  • @Baldrickk: I think I prefer your single-loop solution, which I would rather see _without_ macros, spreading the `for(…)` over several lines and adding (terse) `//`-comments. It would form a slightly odd idiom which one could pick up on a project using it. – PJTraill Jul 04 '17 at 19:50
3

I know this isn't what you want to hear, but I urge you not to do this.

It is perfectly acceptable C style to have a single point of return, before which everything gets cleaned up. Since there are no exceptions, this is easy to do, and easy to verify by looking at the function.

Using macro-hackery or compiler "features" to do this is not accepted C style. It will be a burden for everyone after you to read and understand. And in the end it doesn't actually gain you much.

Dan
  • 12,409
  • 3
  • 50
  • 87
  • Exactly what do you urge the asker not to do? The `__attribute__ ((__cleanup__(free_memory)))` answer does not suffer from your objections. – PJTraill Jul 04 '17 at 19:34
  • 1
    @PJTraill That answer with `__attribute__` is mostly irrelevant since it isn't standard and the OP never mentioned gcc. Now the OP accepted it so I assumed it solved the problem for them, but that's no help to future readers with other compilers. – Lundin Jul 05 '17 at 06:13
  • 1
    Overall it is always good to have one of these "call for sanity" answers to questions like this, even though they don't answer the question. Will prevent others from derailing their programs into some macro hell. – Lundin Jul 05 '17 at 06:14
  • @PJTraill My objection is that it's nonstandard and very unusual, making it a burden to understand. – Dan Jul 05 '17 at 09:44
  • I agree with 'the principle of the least surprise', but since code sandwiches are a _very_ common source of error, I am inclined to scream "extend C with RAII! or else adopt a better idiom!", and happily downvote your answer for it. – xtofl Jul 06 '17 at 07:58