10

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

4 回答 4

12

多么有趣的问题,赞!

经过几次实验,我找到了一个使用GCC__builtin_types_compatible_p__builtin_choose_expr内在函数的解决方案。

__builtin_types_compatible_p

引用 GCC 手册:

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

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

如果类型和(它们是类型,不是表达式)1的非限定版本兼容,则此内置函数返回,否则返回。此内置函数的结果可用于整数常量表达式。type1type20

此内置函数忽略顶级限定符(例如const,, volatile)。例如,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根据常量表达式的值来评估代码。此内置函数返回exp1if 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)

天真的解决方案

现在我们可以根据表达式的实际类型在两个实现之间静态切换。但实际上天真的解决方案是行不通的,见下文。

#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 值替换原始 voidexpr以便能够编译__DO案例(当为 void 时,这无论如何都是死代码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

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

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

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

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

于 2012-09-20T05:25:28.510 回答
1

只要你有typeofand _Generic,你也可以做到没有__builtin_types_compatible_por __builtin_choose_expr

需要注意的是,_Generic不会让你匹配void所以而不是匹配Expr反对void,匹配(typeof(Expr)*){0}反对void*

下面是eldar-abusalimov示例修改为使用_Generic代替__builtin_types_compatible_pand __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;
}
于 2018-10-19T16:34:58.407 回答
0

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

于 2012-09-19T11:51:53.890 回答