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

时间:2012-09-17 04:33:06

标签: c types void c-preprocessor compile-time

让我们说我希望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...

用于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...

因此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__;                                          \
})

4 个答案:

答案 0 :(得分:12)

多么有趣的问题,恭喜!

经过几次实验,我找到了一个使用GCC内含子__builtin_types_compatible_p and __builtin_choose_expr的解决方案。

__builtin_types_compatible_p

引用GCC手册:

  

内置函数:int __builtin_types_compatible_p (type1, type2)

     

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

     

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

     

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

以下是我们如何检查&#34; void ness&#34;。

#define __type_is_void(expr) __builtin_types_compatible_p(typeof(expr), void)

__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内在就像一个&#34;静态开关&#34;在编译时评估。

制备

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

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

#define __DO_VOID(expr) \
    (void) (expr)

天真的解决方案

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

#define DO(expr) \
    __builtin_choose_expr(__type_is_void(expr), \
        __DO_VOID(expr), \
        __DO(expr))  # won't work

尝试编译此代码传递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

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

  

...未使用的表达式(exp1exp2取决于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!

那就是它!以下是Ideone的完整源代码:http://ideone.com/EFy4pE

答案 1 :(得分:2)

你能接受“这不太可能”的答案吗?

不是关于从宏返回的部分。但是关于有条件地测试expr的返回类型的部分。

实际上,您要求的内容如下:

让我们说而不是一些名为“is_expr_type_void(expr)”的神奇检查,而是简单地在调用时传递1或0来表示宏的以下变体中的is_void或!is_void:

#define TIMER(is_void, expr, fmt_msg, ...)                  \
({                                                          \
    struct timeval before, after;                           \
    uint64_t time_span;                                     \
    int time_span_sec, time_span_usec;                      \
    gettimeofday(&before, NULL);                            \
    if (is_void)                                            \
        (expr)                                              \
    else                                                    \
        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, ...);       \
    if (!is_void)                                           \
        _timer_expr__;                                      \
})

这根本行不通。在所有情况下,预处理器都会为if-else条件创建代码,包括void和non-void函数调用。并且双方都会为非空函数编译。但是当使用void函数调用TIMER时,编译器总是会阻塞条件的“else”部分......尽管代码永远不会被调用。

(现在如果存在一个非常聪明的编译器,它既可以识别死代码,也可以在将其标记为编译时错误之前将其删除,那么你很幸运!但我不认为gcc 4.6就是那么聪明......)

这将为您提供#define中#if(is_void)条件的首选选项。但这根本不允许。因为,this answer指出在尝试回答关于条件预处理的类似问题时,预处理器不是turing-complete

所以...尽管你想拥有一个宏,但我认为你最简单的答案是为void函数创建一个,而为返回值的函数创建一个。

答案 2 :(得分:0)

如果您确实需要从宏返回,请改用内嵌功能。

答案 3 :(得分:0)

只要您拥有typeof_Generic,也可以不用__builtin_types_compatible_p__builtin_choose_expr来做到这一点。

需要注意的是,_Generic不会让您与void匹配,因此与其将Exprvoid匹配,不如将(typeof(Expr)*){0}与{{1} }。

下面的eldar-abusalimov示例已修改为使用void*而不是_Generic__builtin_types_compatible_p

__builtin_choose_expr