Cita de la documentación de GCC [1]:el atributo de limpieza se utiliza para ejecutar una función cuando una variable queda fuera de alcance. Este atributo solo se puede aplicar a variables automáticas y no se puede usar con parámetros o con variables estáticas. La función debe tomar un parámetro, un puntero a un tipo compatible con la variable. El valor de retorno de la función, si lo hay, se ignora.
Si la opción -fexceptions está habilitada, la función cleanup_function se inicia cuando la pila se desenrolla, durante el manejo de excepciones. Tenga en cuenta que el atributo de limpieza no detecta excepciones; solo realiza una acción. Si la función de limpieza no vuelve normalmente, el comportamiento es indefinido. El atributo de limpieza es compatible con los compiladores gcc y clang.En este artículo describiré varias opciones para el uso práctico del atributo de limpieza y consideraré la estructura interna de la biblioteca, que usa la limpieza para implementar los análogos std :: unique_ptr y std :: shared_ptr en C.Probemos la limpieza para la desasignación de memoria:
El atributo de limpieza es compatible con los compiladores gcc y clang.En este artículo describiré varias opciones para el uso práctico del atributo de limpieza y consideraré la estructura interna de la biblioteca, que usa la limpieza para implementar los análogos std :: unique_ptr y std :: shared_ptr en C.Probemos la limpieza para la desasignación de memoria:#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));
    
    return 0;
}
Comenzamos, el programa imprime "limpieza realizada". Todo funciona, saludos.Pero un inconveniente se hace evidente de inmediato: no podemos simplemente escribir__attribute__((cleanup(free_int)))
porque la función llamada por el atributo de limpieza debe tomar un puntero a la variable liberada como argumento, y tenemos un puntero al área de memoria asignada, es decir, definitivamente necesitamos una función que tome un doble puntero. Para hacer esto, necesitamos una función de contenedor adicional:static void free_int(int **ptr) 
{
    free(*ptr); 
    ...
}
Además, no podemos usar una función universal para liberar ninguna variable, ya que requerirán diferentes tipos de argumentos. Por lo tanto, reescribimos la función de la siguiente manera:static void _free(void *p) {
    free(*(void**) p);
    printf("cleanup done\n");  
}
Ahora ella puede aceptar cualquier puntero.Aquí hay otra macro útil (de la base del código 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__
que luego se puede usar así:DEFINE_TRIVIAL_CLEANUP_FUNC(FILE*, pclose);
#define _cleanup_pclose_ __attribute__((cleanup(pclosep)))
Pero eso no es todo. Hay una biblioteca que implementa análogos de los plus_ptr únicos y shared_ptr usando este atributo: https://github.com/Snaipe/libcsptrEjemplo de uso (tomado de [2]):#include <stdio.h>
#include <csptr/smart_ptr.h>
#include <csptr/array.h>
void print_int(void *ptr, void *meta) {
    (void) meta;
    
    
    printf("%d\n", *(int*) ptr);
}
int main(void) {
    
    
    smart int *ints = unique_ptr(int[5], {5, 4, 3, 2, 1}, print_int);
    
    
    for (size_t i = 0; i < array_length(ints); ++i) {
        ints[i] = i + 1;
    }
    
    return 0;
}
¡Todo funciona de maravilla!Y veamos qué hay dentro de esta magia. Comencemos con unique_ptr (y shared_ptr al mismo tiempo):# define shared_ptr(Type, ...) smart_ptr(SHARED, Type, __VA_ARGS__)
# define unique_ptr(Type, ...) smart_ptr(UNIQUE, Type, __VA_ARGS__)
Sigamos adelante y veamos qué tan profunda es la madriguera del conejo:# 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;                                                                \
    })
Hasta ahora, la claridad no ha aumentado, ante nosotros hay un revoltijo de macros en las mejores tradiciones de este lenguaje. Pero no estamos acostumbrados a retirarnos. Desentrañar el enredo:define CSPTR_SENTINEL        .sentinel_ = 0,
define CSPTR_SENTINEL_DEC int sentinel_;
...
typedef void (*f_destructor)(void *, void *);
Realizar la sustitución:# 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;                                                                \
    })
e intenta entender lo que está pasando aquí. Tenemos una determinada estructura, que consta de la variable centinela_, una determinada matriz (Tipo) [Longitud], un puntero a una función destructora, que se pasa en la parte adicional (...) de los argumentos de macro, y una metaestructura, que también está llena de argumentos adicionales. Lo siguiente es una llamadasmalloc(sizeof (Type), Length, Kind, ARGS_);
¿Qué es smalloc? Encontramos más magia de plantilla (ya he hecho algunas sustituciones aquí):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__ })
Bueno, por eso amamos a C. También hay documentación en la biblioteca (gente santa, recomiendo a todos que tomen un ejemplo de ellos): lafunción smalloc () llama al asignador (malloc (3) por defecto), el puntero devuelto es un puntero "inteligente". <...> Si el tamaño es 0, se devuelve NULL. Si nmemb es 0, smalloc devolverá un puntero inteligente a un bloque de memoria de al menos bytes de tamaño, y un puntero escalar inteligente, si nmemb no es igual a 0, se devuelve un puntero a un bloque de memoria de tamaño al menos tamaño * nmemb y el puntero es de tipo matriz.original«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.»
 Aquí está la fuente de smalloc:__attribute__ ((malloc)) void *smalloc(s_smalloc_args *args) {
    return (args->nmemb == 0 ? smalloc_impl : smalloc_array)(args);
}
Veamos el código smalloc_impl, asignando objetos de tipos escalares. Para reducir el volumen, eliminé el código asociado con los punteros compartidos e hice una sustitución en línea y macro:static void *smalloc_impl(s_smalloc_args *args) {
    if (!args->size)
        return NULL;
    
    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;
}
Aquí vemos que se asigna la memoria para la variable, más un encabezado determinado de tipo s_meta más un área de metadatos de tamaño args-> meta.size alineado con el tamaño de la palabra, más una palabra más (sizeof (size_t)). La función devuelve un puntero al área de memoria de la variable: ptr + head_size + lined_metasize + 1. Asignemosuna variable de tipo int, inicializada con el valor 42:smart void *ptr = unique_ptr(int, 42);
Aquí inteligente es una macro:# define smart __attribute__ ((cleanup(sfree_stack)))
Cuando el puntero abandona el alcance, se llama 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;
}
Función sfree (abreviada):void sfree(void *ptr) {
    s_meta *meta = get_meta(ptr);
    dealloc_entry(meta, ptr);
}
La función dealloc_entry, básicamente, llama a un destructor personalizado si lo especificamos en los argumentos de unique_ptr, y el puntero se almacena en metadatos. Si no, solo se ejecuta free (meta).Lista de fuentes:[1] Atributos variables comunes .[2] Una forma buena e idiomática de utilizar GCC y clang __attribute __ ((limpieza)) y declaraciones de puntero .[3] Usando el atributo variable __cleanup__ en GCC .