清理属性

引用GCC文档[1]:

当变量超出范围时,cleanup属性用于运行函数。此属性只能应用于自动变量,不能与参数或静态变量一起使用。该函数必须采用一个参数,即指向与该变量兼容的类型的指针。该函数的返回值(如果有)将被忽略。

如果启用了-fexceptions选项,则在处理异常时,在展开堆栈时将启动cleanup_function函数。请注意,cleanup属性不会捕获异常;它只会执行一个操作。如果cleanup_function无法正常返回,则行为未定义。




gcc和clang编译器支持cleanup属性。

在本文中,我将描述实际使用cleanup属性的各种选项,并考虑库的内部结构,该库使用cleanup在C中实现std :: unique_ptr和std :: shared_ptr类似物。

让我们尝试清理内存释放:

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

static void free_int(int **ptr) 
{
    free(*ptr); 
    printf("cleanup done\n");
}

int main()
{
    __attribute__((cleanup(free_int))) int *ptr_one = (int *)malloc(sizeof(int));
    // do something here
    return 0;
}

我们开始,程序打印“清理完成”。一切正常,欢呼。

但是一个缺点立即变得显而易见:我们不能简单地写

__attribute__((cleanup(free_int)))

因为cleanup属性调用的函数必须将指向释放变量的指针作为参数,并且我们有一个指向已分配内存区域的指针,也就是说,我们肯定需要一个带有双指针的函数。为此,我们需要一个附加的包装函数:

static void free_int(int **ptr) 
{
    free(*ptr); 
    ...
}

另外,我们不能使用通用函数来释放任何变量,因为它们将需要不同类型的参数。因此,我们将函数重写如下:

static void _free(void *p) {
    free(*(void**) p);
    printf("cleanup done\n");  
}

现在她可以接受任何指针。

这是另一个有用的宏(来自systemd代码库):

#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__

以后可以这样使用:

DEFINE_TRIVIAL_CLEANUP_FUNC(FILE*, pclose);
#define _cleanup_pclose_ __attribute__((cleanup(pclosep)))

但这还不是全部。有一个使用此属性实现unique_ptr和shared_ptr plus的类似物的库:https : //github.com/Snaipe/libcs​​ptr

使用示例(摘自[2]):

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

一切正常!

让我们看看这个魔术里面有什么。让我们从unique_ptr(和shared_ptr同时开始)开始:

# define shared_ptr(Type, ...) smart_ptr(SHARED, Type, __VA_ARGS__)
# define unique_ptr(Type, ...) smart_ptr(UNIQUE, Type, __VA_ARGS__)

让我们继续前进,看看兔子洞有多深:

# define smart_arr(Kind, Type, Length, ...)                                 \
    ({                                                                      \
        struct s_tmp {                                                      \
            CSPTR_SENTINEL_DEC                                              \
            __typeof__(__typeof__(Type)[Length]) value;                     \
            f_destructor dtor;                                              \
            struct {                                                        \
                const void *ptr;                                            \
                size_t size;                                                \
            } meta;                                                         \
        } args = {                                                          \
            CSPTR_SENTINEL                                                  \
            __VA_ARGS__                                                     \
        };                                                                  \
        void *var = smalloc(sizeof (Type), Length, Kind, ARGS_);            \
        if (var != NULL)                                                    \
            memcpy(var, &args.value, sizeof (Type));                        \
        var;                                                                \
    })

到目前为止,清晰度还没有增加,在我们面前是这门语言的最佳传统中的一堆宏指令。但是我们不习惯撤退。解开缠结:

define CSPTR_SENTINEL        .sentinel_ = 0,
define CSPTR_SENTINEL_DEC int sentinel_;
...
typedef void (*f_destructor)(void *, void *);

执行替换:

# define smart_arr(Kind, Type, Length, ...)                                 \
    ({                                                                      \
        struct s_tmp {                                                      \
            int sentinel_;                                                  \
            __typeof__(__typeof__(Type)[Length]) value;                     \
            void (*)(void *, void *) dtor;                                  \
            struct {                                                        \
                const void *ptr;                                            \
                size_t size;                                                \
            } meta;                                                         \
        } args = {                                                          \
            .sentinel_ = 0,                                                 \
            __VA_ARGS__                                                     \
        };                                                                  \
        void *var = smalloc(sizeof (Type), Length, Kind, ARGS_);            \
        if (var != NULL)                                                    \
            memcpy(var, &args.value, sizeof (Type));                        \
        var;                                                                \
    })

并尝试了解这里发生的情况。我们有一个确定的结构,由sentinel_变量,某个数组(类型)[Length],指向析构函数的指针(该宏传递给宏参数的附加(...)部分)和一个元结构(也由附加的参数组成)组成。接下来是电话

smalloc(sizeof (Type), Length, Kind, ARGS_);

什么是smalloc?我们发现更多的模板魔术(我已经在这里做了一些替换):

enum pointer_kind {
    UNIQUE,
    SHARED,
    ARRAY = 1 << 8
};
//..
typedef struct {
    CSPTR_SENTINEL_DEC
    size_t size;
    size_t nmemb;
    enum pointer_kind kind;
    f_destructor dtor;
    struct {
        const void *data;
        size_t size;
    } meta;
} s_smalloc_args;
//...
__attribute__ ((malloc)) void *smalloc(s_smalloc_args *args);
//...
#  define smalloc(...) \
    smalloc(&(s_smalloc_args) { CSPTR_SENTINEL __VA_ARGS__ })

好吧,这就是我们爱C的原因。库中还有文档(神圣的人,我建议每个人都从他们那里举个例子):smalloc()

函数调用分配器(默认情况下为malloc(3)),返回的指针是“智能”指针。<...>如果size为0,则返回NULL。如果nmemb为0,则smalloc将返回至少大小为字节的存储块的智能指针,如果nmemb不等于0,则返回为至少大小为* nmemb的存储块的指针,并且为智能标量指针。数组类型为指针。

原版的
«The smalloc() function calls an allocator (malloc (3) by default), such that the returned pointer is a smart pointer. <...> If size is 0, then smalloc() returns NULL. If nmemb is 0, then smalloc shall return a smart pointer to a memory block of at least size bytes, and the smart pointer is a scalar. Otherwise, it shall return a memory block to at least size * nmemb bytes, and the smart pointer is an array.»

这是smalloc的来源:

__attribute__ ((malloc)) void *smalloc(s_smalloc_args *args) {
    return (args->nmemb == 0 ? smalloc_impl : smalloc_array)(args);
}

让我们看一下代码smalloc_impl,它分配标量类型的对象。为了减少体积,我删除了与共享指针关联的代码,并进行了内联和宏替换:

static void *smalloc_impl(s_smalloc_args *args) {
    if (!args->size)
        return NULL;

    // align the sizes to the size of a word
    size_t aligned_metasize = align(args->meta.size);
    size_t size = align(args->size);

    size_t head_size = sizeof (s_meta);
    s_meta_shared *ptr = malloc(head_size + size + aligned_metasize + sizeof (size_t));

    if (ptr == NULL)
        return NULL;

    char *shifted = (char *) ptr + head_size;
    if (args->meta.size && args->meta.data)
        memcpy(shifted, args->meta.data, args->meta.size);

    size_t *sz = (size_t *) (shifted + aligned_metasize);
    *sz = head_size + aligned_metasize;

    *(s_meta*) ptr = (s_meta) {
        .kind = args->kind,
        .dtor = args->dtor,
        .ptr = sz + 1
    };

    return sz + 1;
}

在这里我们看到分配了变量的内存,加上某个类型为s_meta的标头,以及一个与单词的大小对齐的size args-> meta.size的元数据区域,再加上一个单词(sizeof(size_t))。该函数返回一个指向变量内存的指针:ptr + head_size + aligned_metasize + 1。

让我们分配一个int类型的变量,用值42初始化:

smart void *ptr = unique_ptr(int, 42);

这是一个宏:

# define smart __attribute__ ((cleanup(sfree_stack)))

当指针离开作用域时,将调用sfree_stack:

CSPTR_INLINE void sfree_stack(void *ptr) {
    union {
        void **real_ptr;
        void *ptr;
    } conv;
    conv.ptr = ptr;
    sfree(*conv.real_ptr);
    *conv.real_ptr = NULL;
}

Sfree功能(缩写):

void sfree(void *ptr) {
    s_meta *meta = get_meta(ptr);
    dealloc_entry(meta, ptr);
}

如果我们在unique_ptr参数中指定了dealloc_entry函数,则该函数基本上会调用自定义析构函数,并将其指针存储在元数据中。如果不是,则仅执行免费(元)。

来源列表:

[1] 通用变量属性
[2] 一种使用GCC和clang __attribute __((cleanup))和指针声明的好方法
[3] 在GCC中使用__cleanup__变量属性

All Articles