I think that GCC extension __attribute__((cleanup))
is a good idea, at least for some cases, but i can't figure out how to use it in a good way. All i'm doing looks still really annoying.
I saw a lot of code doing #define _cleanup_(x) __attribute__((cleanup(x))
just to type less, but it there a way to pass there a standard function like free
or closedir
, fclose
, etc?
As I see I can't just write:
__attribute__((cleanup(free))) char *foo = malloc(10);
Because the cleanup callback will receive char**
pointer, and I have to always write something like:
static void free_char(char **ptr) { free(*ptr); }
__cleanup__((free_char)) char *foo = malloc(10);
That's pretty annoying, and the most annoying part is to define such cleanup functions for all types you need, because obviously you can't just define it for void **
. What is the best way to avoid these things?
There's a library that builds general-purpose smart pointers (unique_ptr
and shared_ptr
) on top of __attribute__((cleanup))
here: https://github.com/Snaipe/libcsptr
It allows you to write higher-level code like this:
#include <stdio.h>
#include <csptr/smart_ptr.h>
#include <csptr/array.h>
void print_int(void *ptr, void *meta) {
(void) meta;
// ptr points to the current element
// meta points to the array metadata (global to the array), if any.
printf("%d\n", *(int*) ptr);
}
int main(void) {
// Destructors for array types are run on every element of the
// array before destruction.
smart int *ints = unique_ptr(int[5], {5, 4, 3, 2, 1}, print_int);
// ints == {5, 4, 3, 2, 1}
// Smart arrays are length-aware
for (size_t i = 0; i < array_length(ints); ++i) {
ints[i] = i + 1;
}
// ints == {1, 2, 3, 4, 5}
return 0;
}
As for idiomatic, though? Well the above is certainly close to idiomatic C++. Not C so much. The feature is clearly mainly supported in GCC and Clang because they have C++ compilers as well, so they have the option to make use of the RAII machinery in the C frontend at no extra cost; that doesn't make it a great idea to write C-intended-as-C this way. It kinda relies on a C++ compiler being present despite not actually being used.
If it were me, I'd probably investigate implementing autorelease pools, or something similar that can actually be done in pure C at the language level. Depends how quickly you need your resources to be freed; for memory, you usually can live without immediate cleanup.
You can't write __attribute__((cleanup(free)))
, but you don't need to write a free
cleanup function for each type. It's ugly, but you can write this:
static void cleanup_free(void *p) {
free(*(void**) p);
}
I first saw this in the systemd
codebase.
For other functions you would in general need to a write a wrapper with an extra level of indirection for use with __attribute__((cleanup))
. systemd
defines a helper macro for this:
#define DEFINE_TRIVIAL_CLEANUP_FUNC(type, func) \
static inline void func##p(type *p) { \
if (*p) \
func(*p); \
} \
struct __useless_struct_to_allow_trailing_semicolon__
which is used all over the place, e.g.
DEFINE_TRIVIAL_CLEANUP_FUNC(FILE*, pclose);
#define _cleanup_pclose_ __attribute__((cleanup(pclosep)))