5

I'm trying to work through an issue on a third party library. The issue is the library uses GCC's nested functions buried in a macro, and Clang does not support nested functions and has no plans to do so (cf., Clang Bug 6378 - error: illegal storage class on function).

Here's the macro that's the pain point for me and Clang:

#define RAII_VAR(vartype, varname, initval, dtor) \
    /* Prototype needed due to http://gcc.gnu.org/bugzilla/show_bug.cgi?id=36774 */ \
    auto void _dtor_ ## varname (vartype * v); \
    void _dtor_ ## varname (vartype * v) { dtor(*v); } \
    vartype varname __attribute__((cleanup(_dtor_ ## varname))) = (initval)

And here's how its used (from the code comments):

 * void do_stuff(const char *name)
 * {
 *     RAII_VAR(struct mything *, thing, find_mything(name), ao2_cleanup);
 *     if (!thing) {
 *         return;
 *     }
 *     if (error) {
 *         return;
 *     }
 *     do_stuff_with_thing(thing);
 * }

The Clang User Manual states to use C++ and a lambda function to emulate. I'm not sure that's the best strategy, and a C project will likely not accept a C++ patch (they would probably tar and feather me first).

Is there a way to rewrite the macro so that's its (1) more accommodating to Clang, and (2) preserves original function semantics?

4

2 回答 2

6

Clang 不支持 GCC 嵌套函数,但它支持Objective C 风格的“块”,即使在 C 模式下:

void f(void * d) {
    void (^g)(void *) = ^(void * d){ };
    g(d);
}

您需要使用clang命令而不是gcc, 来调用它,并且还需要 (?) 将其传递-fblocks -lBlocksRuntime给编译器。

您不能cleanup直接将块用作值,因为它必须是函数名称,因此(从这里窃取想法)您需要添加一层间接。定义一个函数来清理 void 块,并使您的 RAII 变量成为您想要在范围末尾运行的块:

typedef void (^cleanup_block)(void);
static inline void do_cleanup(cleanup_block * b) { (*b)(); }

void do_stuff(const char *name) {
    cleanup_block __attribute__((cleanup(do_cleanup))) __b = ^{ };
}

因为块形成闭包,所以您可以将操作放在变量上以直接在块内进行清理......

void do_stuff(const char *name) {
    struct mything * thing;
    cleanup_block __attribute__((cleanup(do_cleanup))) __b = ^{ ao2_cleanup(thing); };
}

...并且应该像以前一样在范围的末尾运行,由块上的清理调用。重新排列宏并添加 a__LINE__使其适用于多个声明:

#define CAT(A, B) CAT_(A, B)
#define CAT_(A, B) A##B

#define RAII_VAR(vartype, varname, initval, dtor) \
    vartype varname = (initval); \
    cleanup_block __attribute__((cleanup(do_cleanup))) CAT(__b_, __LINE__) = ^{ dtor(varname); };

void do_stuff(const char *name) {
    RAII_VAR(struct mything *, thing, NULL, ao2_cleanup);
    ...

反正就是这样。

于 2014-07-25T16:45:07.193 回答
1

我相信你可以在不使用特定于 clang 的版本的情况下做到这一点,我会尝试这样的事情(未经测试,可能需要一些额外的演员表):

struct __destructor_data {
    void (*func)(void *);
    void **data;
}

static inline __destructor(struct __destructor_data *data)
{
    data->func(*data->data);
}

#define RAII_VAR(vartype, varname, initval, dtor)  \
    vartype varname = initval;                     \
    __attribute((cleanup(__destructor)))           \
        struct __destructor_data __dd ## varname = \
             { dtor, &varname };

在我们的项目中,我们_auto_(dtor)在普通变量声明之前有一个特定于 gcc 的宏,例如:

_auto_(free) char *str = strdup("hello");

在这种情况下,我们的宏不能在变量声明之后添加任何东西,也不知道变量的名称,所以为了避免使用 gcc 特定的嵌套函数,我想出了以下 hackish 版本,以防这对任何人都有帮助:

static void *__autodestruct_value = NULL;
static void (*__autodestruct_dtor)(void *) = NULL;

static inline void __autodestruct_save_dtor(void **dtor)
{
       __autodestruct_dtor = *dtor;
       __autodestruct_dtor(__autodestruct_value);
}

static inline void __autodestruct_save_value(void *data)
{
       __autodestruct_value = *(void **) data;
}

#define __AUTODESTRUCT(var, func)                              \
       __attribute((cleanup(__autodestruct_save_dtor)))      \
               void *__dtor ## var = (void (*)(void *))(func); \
       __attribute((cleanup(__autodestruct_save_value)))
 
#define _AUTODESTRUCT(var, func)                       \
       __AUTODESTRUCT(var, func)

#define _auto_(func)                                    \
        _AUTODESTRUCT(__COUNTER__, func)

这是骇人听闻的,因为它取决于编译器调用析构函数的顺序与声明的顺序相反,并且与 gcc 特定版本相比,它有一些明显的缺点,但它适用于两种编译器。

于 2021-05-17T22:01:36.363 回答