使用 GCC 和 clang __attribute__((cleanup)) 和指针声明的良好且惯用的方法

A good and idiomatic way to use GCC and clang __attribute__((cleanup)) and pointer declarations

我认为 GCC 扩展 __attribute__((cleanup)) 是个好主意,至少在某些情况下是这样,但我不知道如何以好的方式使用它。我所做的一切看起来仍然很烦人。

我看到很多代码做 #define _cleanup_(x) __attribute__((cleanup(x)) 只是为了减少输入,但它有一种方法可以传递标准函数,如 freeclosedirfclose, 等等?

据我所知,我不能只写:

__attribute__((cleanup(free))) char *foo = malloc(10);

因为清理回调会收到 char** 指针,所以我必须总是这样写:

static void free_char(char **ptr) { free(*ptr); }
__cleanup__((free_char)) char *foo = malloc(10);

这很烦人,最烦人的部分是为你需要的所有类型定义这样的清理函数,因为显然你不能只为 void ** 定义它。避免这些事情的最佳方法是什么?

你可以不写__attribute__((cleanup(free))),但是你不需要为每个类型都写一个free清理函数。这很丑,但你可以这样写:

static void cleanup_free(void *p) {
  free(*(void**) p);
}

我第一次看到这个in the systemd codebase

对于其他函数,您通常需要编写一个具有额外间接级别的包装器,以便与 __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__

用于all over the place,例如

DEFINE_TRIVIAL_CLEANUP_FUNC(FILE*, pclose);

#define _cleanup_pclose_ __attribute__((cleanup(pclosep)))

这里有一个在 __attribute__((cleanup)) 之上构建 general-purpose 智能指针(unique_ptrshared_ptr)的库:https://github.com/Snaipe/libcsptr

它允许您编写 higher-level 代码,如下所示:

#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;
}

不过至于地道?好吧,以上肯定接近惯用的 C++。不是C那么多。该功能显然主要在 GCC 和 Clang 中得到支持,因为它们也有 C++ 编译器,因此它们可以选择在 C 前端使用 RAII 机制,无需额外费用;这样写 C-intended-as-C 并不是一个好主意。它有点依赖于 存在 的 C++ 编译器,尽管实际上并未 使用 .

如果是我,我可能会研究实现自动释放池,或者类似的东西,实际上可以在语言级别用纯 C 完成。取决于您需要多快释放资源;对于内存,您通常可以不立即清理。