【问题标题】:How to check `typeof` for void value at compile time?如何在编译时检查 `typeof` 的 void 值?
【发布时间】:2012-09-09 07:25:49
【问题描述】:

假设我想要一个适用于任何类型的 C 宏。 我正在使用 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__;                                          \
})

【问题讨论】:

  • 我认为这在 C 中是不可能的。我在 C++ 中有一个类似的问题,它需要仅使用 C++ 的方法。如果您有兴趣,请联系this one
  • 谢谢,克里斯,但我仅限于 C(99)。在发布此问题之前,我实际上已经(不仅)阅读了您的帖子。 :)
  • 宏不能“返回”值。不过,您也许可以使用逗号运算符来模拟它。
  • Joachim:你甚至可以声明 LAMBDA 宏并将其用作 qsort(arr, n, size, LAMBDA(int, (x, y) { x>y }); 即stackoverflow.com/a/3326424/64062跨度>
  • 在 gcc 中使用-finstrument-functions 选项怎么样? demonstration.

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


【解决方案1】:

只要你有typeof_Generic,你也可以不使用__builtin_types_compatible_p__builtin_choose_expr

需要注意的是,_Generic 不会让您匹配 void,因此不要匹配 Exprvoid,而是匹配 (typeof(Expr)*){0}void*

下面是eldar-abusalimov 示例修改为使用_Generic 而不是__builtin_types_compatible_p__builtin_choose_expr

#include <stdio.h>
#define __type_is_void(expr) _Generic((typeof(expr)*){0}, void*:1, default:0)
#define __expr_or_zero(expr) _Generic((typeof(expr)*){0}, void*:0, default:(expr))

#define DO(expr) \
    _Generic((typeof(expr)*){0}, \
        void*:__DO_VOID(expr), \
        default:__DO(__expr_or_zero(expr)))

#define __DO(expr) \
    ({ typeof(expr) __ret; puts("do nonvoid"); __ret = (expr); __ret; })

#define __DO_VOID(expr) \
    (void)({ puts("do void"); (void)(expr); })

void foo(void) { }
int bar(void) { return 1; }

int main(void) 
{
    DO(foo());
    DO(bar());
    return 0;
}

【讨论】:

  • 在这种情况下实际上不需要 __type_is_void 宏
【解决方案2】:

多么有趣的问题,赞!

经过几次实验,我找到了一个使用 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判断两种类型是否相同。

如果类型 type1type2(它们是类型,不是表达式)的非限定版本兼容,则此内置函数返回 1,否则返回 0这个内置函数的结果可以用在整数常量表达式中。

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

以下是我们检查“voidness”的方法。

#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 根据常量表达式的值来评估代码。如果const_exp(整数常量表达式)不为零,则此内置函数返回exp1。否则返回exp2

这个内置函数类似于 C 中的 ? : 运算符,除了 返回的表达式的类型不受提升规则的影响。此外,内置函数不会评估未选择的表达式。例如,如果 const_exp 的计算结果为 true,则即使有副作用,exp2 也不会被计算。

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

所以__builtin_choose_expr intrinsic 类似于在编译时评估的“静态开关”。

准备

我不会在此处粘贴您的 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 为 void 时,这无论如何都是死代码)。

#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

【讨论】:

  • 嘿。我现在刚刚测试过。这太疯狂了,它有效。我喜欢你。 :-)
【解决方案3】:

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

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

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

假设您在调用时传递一个 1 或 0 来指示 is_void 或 !is_void 在您的宏的以下变体中,而不是一些称为“is_expr_type_void(expr)”的神奇检查:

#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 和非 void 函数调用。并且双方都可以很好地编译非 void 函数。但是当使用 void 函数调用 TIMER 时,编译器总是会阻塞条件的“else”部分……尽管代码永远不会被调用。

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

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

所以……尽管您希望拥有一个宏,但我认为您最简单的答案是为 void 函数创建一个,为具有返回值的函数创建一个。

【讨论】:

  • 是的,约翰,这种推理也是我想出来的。我已经把它作为两个宏,只是希望有人能通过我不知道的方式神奇地解决这个问题。但是你的答案是正确的,你应该得到赏金...... :-)
  • 这是我使用单个宏的 GCC 解决方案:stackoverflow.com/a/15448781/545027
【解决方案4】:

如果确实需要从宏返回,请改用内联函数。

【讨论】:

  • 从宏返回不是问题。问题是不返回(分别“保存”类型为 void 的值)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-09-04
  • 1970-01-01
  • 2012-06-10
  • 1970-01-01
  • 1970-01-01
  • 2014-11-24
  • 1970-01-01
相关资源
最近更新 更多