如何在编译时检查`typeof`是否为void值?

Din*_*lla 10 c types void compile-time c-preprocessor

让我们说我希望C macro适用于任何类型.我正在使用GCC编译器(> = 4.6)并且可以使用GNU99宏.

//code...
any_type_t *retVal = function_that_runs_very_long_time(a, b, &&c, **d, &e, *f);
//other code...
Run Code Online (Sandbox Code Playgroud)

用于TIMER的宏的用法可以看起来像这样

//code...
any_type_t *retVal = 
    TIMER(
          function_that_runs_very_long_time(a, b, &&c, **d, &e, *f),
          "TIMING FOR VALUE <%d, %d>", a, b
         );
//other code...
Run Code Online (Sandbox Code Playgroud)

因此TIMER必须返回给定函数的值和其运行的打印持续时间.具有void返回类型的函数存在问题.

我显然可以有两个宏,如TIMER_TYPE和TIMER_VOID,但我想使用单一的时间函数与任何返回值.

谢谢你的建议.


编辑此TIMER宏的示例

#define TIMER(expr, fmt_msg, ...)                           \
({                                                          \
    struct timeval before, after;                           \
    uint64_t time_span;                                     \
    int time_span_sec, time_span_usec;                      \
    gettimeofday(&before, NULL);                            \
    typeof(expr) _timer_expr__ = (expr);                    \ // <- static if?
    gettimeofday(&after, NULL);                             \
    time_span = (after.tv_sec * 1000000 + after.tv_usec)    \
              - (before.tv_sec * 1000000 + before.tv_usec); \
    time_span_sec  = time_span / 1000000;                   \
    time_span_usec = time_span % 1000000;                   \
    TRACE(fmt_msg "\n%s : %d.%d seconds",                   \
          #expr, time_span_sec, time_span_usec, ...);       \
    _timer_expr__;                                          \
})
Run Code Online (Sandbox Code Playgroud)

Eld*_*mov 12

多么有趣的问题,恭喜!

几个实验后,我发现它使用的解决方案__builtin_types_compatible_p和__builtin_choose_expr海湾合作委员会的内部函数.

__builtin_types_compatible_p

引用GCC手册:

内置功能: int __builtin_types_compatible_p (type1, type2)

您可以使用内置函数__builtin_types_compatible_p来确定两种类型是否相同.

1如果类型的非限定版本type1和type2(类型,而不是表达式)兼容,0则此内置函数返回.此内置函数的结果可用于整型常量表达式.

此内置函数忽略顶级限定符(例如const,volatile).例如,int相当于const int.

所以这就是我们如何检查" voidness".

#define __type_is_void(expr) __builtin_types_compatible_p(typeof(expr), void)
Run Code Online (Sandbox Code Playgroud)

__builtin_choose_expr

内置功能: type __builtin_choose_expr (const_exp, exp1, exp2)

您可以使用内置函数__builtin_choose_expr根据常量表达式的值来评估代码.exp1如果const_exp(整数常量表达式)非零,则此内置函数返回.否则它会返回exp2.

此内置函数类似于? :C中的运算符,除了返回的表达式的类型未按升级规则更改.此外,内置函数不会评估未选择的表达式.例如,如果const_exp计算结果为true,exp2即使它有副作用,也不会进行评估.

如果exp1返回,则返回类型与类型相同exp1.同样,如果exp2返回,则其返回类型与exp2.

因此__builtin_choose_expr内在类似于在编译时评估的"静态开关".

制备

我不会在这里粘贴你的TIMER宏,但我认为它能够将它分成两个版本:一个用于void expr,另一个用于其余版本.这里只是存根,它们评估表达式并产生相同类型的结果.

#define __DO(expr) \
    ({ typeof(expr) __ret; __ret = (expr); __ret; })

#define __DO_VOID(expr) \
    (void) (expr)
Run Code Online (Sandbox Code Playgroud)

天真的解决方案

现在我们可以在两个实现之间静态切换,具体取决于表达式的实际类型.但事实上,天真的解决方案不起作用,见下文.

#define DO(expr) \
    __builtin_choose_expr(__type_is_void(expr), \
        __DO_VOID(expr), \
        __DO(expr))  # won't work
Run Code Online (Sandbox Code Playgroud)

尝试编译此代码传递void表达式会出现以下错误:

test.c:28:9: error: variable or field ‘__ret’ declared void
test.c:28:9: error: void value not ignored as it ought to be
Run Code Online (Sandbox Code Playgroud)

虽然__DO_VOID选择了,但会__DO产生错误.手动中描述了此行为:

...未使用的表达式(exp1或exp2取决于值const_exp)仍可能生成语法错误.这可能会在将来的修订中发生变化.

工作方案

诀窍是用原始的void expr替换一些非void值来编译__DO案例(当expr无效时无论如何都是死代码).

#define __expr_or_zero(expr) __builtin_choose_expr(__type_is_void(expr), 0, (expr))

#define DO(expr) \
    __builtin_choose_expr(__type_is_void(expr), \
        __DO_VOID(expr), \
        __DO(__expr_or_zero(expr))) # works fine!
Run Code Online (Sandbox Code Playgroud)

而已!以下是Ideone的完整源代码:http://ideone.com/EFy4pE