用Clang的嵌套函数重写GCC清理宏?

时间:2014-07-25 15:38:10

标签: c gcc macros clang gcc-extensions

我正在尝试解决第三方库中的问题。问题是库使用GCC的嵌套函数隐藏在宏中,而Clang不支持嵌套函数,也没有计划这样做(参见,Clang Bug 6378 - error: illegal storage class on function)。

这就是我和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)

以下是它的使用方法(来自代码注释):

 * 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);
 * }

Clang User Manua l声明使用C ++和lambda函数进行模拟。我不确定这是最好的策略,而C项目很可能接受C ++补丁(他们可能会首先考虑并惹我生气)。

有没有办法重写宏以使其(1)更适应Clang,以及(2)保留原始函数语义?

2 个答案:

答案 0 :(得分:6)

Clang不支持GCC嵌套函数,但它支持Objective C-style "blocks",即使在C模式下也是如此:

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

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

您不能直接将块用作cleanup值,因为它必须是函数名称,因此(从here窃取想法)您需要添加一层间接。定义一个函数来清理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); };
}

...并且应该像以前一样在范围的末尾运行,由块上的清理调用。重新排列宏并添加__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);
    ...

无论如何都是这样的。

答案 1 :(得分: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 };

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

_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)

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