A good and idiomatic way to use GCC and clang __at

2019-04-18 12:29发布

问题:

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?

回答1:

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.



回答2:

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


标签: c gcc clang