Подтвердить что ты не робот

Хороший и идиоматический способ использования GCC и clang __attribute __ ((очистка)) и декларации указателей

Я думаю, что расширение GCC __attribute__((cleanup)) - это хорошая идея, по крайней мере для некоторых случаев, но я не могу понять, как используйте его в хорошем смысле. Все, что я делаю, выглядит очень раздражающе.

Я видел много кода, делающих #define _cleanup_(x) __attribute__((cleanup(x)), просто для ввода меньше, но там есть способ передать там стандартную функцию, например free или closedir, fclose и т.д.

Как я вижу, я не могу просто написать:

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

Так как обратный вызов очистки получит char** указатель, и я должен всегда писать что-то вроде:

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

Это очень раздражает, и наиболее раздражающей частью является определение таких функций очистки для всех типов, которые вам нужны, потому что, очевидно, вы не можете просто определить его для void **. Каков наилучший способ избежать этих вещей?

4b9b3361

Ответ 1

Там есть библиотека, которая строит интеллектуальные указатели общего назначения (unique_ptr и shared_ptr) поверх __attribute__((cleanup)) здесь: 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;
}

Что же касается идиоматического, то? Ну, это, безусловно, близко к идиоматическому С++. Не так много. Эта функция, очевидно, в основном поддерживается в GCC и Clang, поскольку у них также есть компиляторы С++, поэтому они могут использовать систему RAII в интерфейсе C без каких-либо дополнительных затрат; это не делает отличной идеей писать C-предназначенный как-C таким образом. Это похоже на компилятор С++, который присутствует, хотя фактически не используется.

Если бы это был я, я бы, вероятно, исследовал внедрение пулов авторекламы или что-то подобное, что действительно можно сделать в чистом C на уровне языка. Зависит от того, насколько быстро вам нужны ваши ресурсы для освобождения; для памяти вы обычно можете жить без немедленной очистки.

Ответ 2

Вы не можете написать __attribute__((cleanup(free))), но вам не нужно писать функцию очистки free для каждого типа. Это уродливо, но вы можете написать это:

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

Я впервые увидел это в systemd codebase.

Для других функций вам в целом понадобится написать оболочку с дополнительным уровнем косвенности для использования с __attribute__((cleanup)). 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)))