【问题标题】:A good and idiomatic way to use GCC and clang __attribute__((cleanup)) and pointer declarations使用 GCC 和 clang __attribute__((cleanup)) 和指针声明的一种很好的惯用方式
【发布时间】:2016-04-07 02:36:06
【问题描述】:

我认为 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))不是是一个好主意是一个很好的理由。
  • 如果你想要析构函数,你知道在哪里可以找到它们。
  • @user3386109,我会说这是个好主意,但实现有点破。或者可能有一些我不明白的东西,所以这就是我问的原因。
  • @coredump 实现也很好。清理函数需要一个指向变量的 pointer 是有道理的,因为变量可能不是指针本身,而是一个最好不要按值传递的大型结构(参见 glib 的 g_autoptr 之间的区别和g_auto)。仅仅为了方便而拥有两个不同的属性会使语言扩展不必要地膨胀。
  • @RossRidge:控制它比担心 C++ 带来的包袱要好。所以是的,如果我们想要合理的、好的、非隐藏的析构函数,我们知道在哪里可以找到它们:gcc 和 clang 下的 C 代码。

标签: c gcc clang


【解决方案1】:

这里有一个库可以在__attribute__((cleanup)) 之上构建通用智能指针(unique_ptrshared_ptr):https://github.com/Snaipe/libcsptr

它允许你像这样编写更高级别的代码:

#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 语言级别上完成。取决于您需要多快释放资源;为了记忆,你通常可以不用立即清理。

【讨论】:

    【解决方案2】:

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

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

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

    对于其他函数,您通常需要编写一个带有额外间接级别的包装器,以便与__attribute__((cleanup)) 一起使用。 systemddefines 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)))
    

    【讨论】:

    最近更新 更多