【问题标题】:Using defined(MACRO) inside the C if statement在 C if 语句中使用 defined(MACRO)
【发布时间】:2011-07-24 18:21:15
【问题描述】:

我想用 C 编写类似这样的代码:

如果(定义(宏)) ... 别的 ...

但我在 C 中找不到任何方法来执行此操作,因为已定义(MACRO)预处理器运算符仅在#if 内部工作。有没有办法做到这一点?

我真正喜欢做的是写作:

断言(UART, var >= 0);

在哪里

#define 断言(名称,测试)\ 做 { \ 如果(定义(名称)&&!(测试))\ printf("断言失败"); \ } 而(0)

因此我可以在定义宏时打开 ASSERT 检查,如果未定义,则不应检查断言。如果你尝试这样做,那么你会得到:

函数`defined'的隐式声明

这很容易理解,因为 GCC 编译器没有找到 defined() 预处理器运算符。

【问题讨论】:

标签: gcc c-preprocessor


【解决方案1】:

comex 的宏如果参数定义为 1,则扩展为 1。否则扩展为 0:

#define is_set(macro) is_set_(macro)
#define macrotest_1 ,
#define is_set_(value) is_set__(macrotest_##value)
#define is_set__(comma) is_set___(comma 1, 0)
#define is_set___(_, v, ...) v

您可以按如下方式使用它:

if (is_set(MACRO)) {
   /* Do something when MACRO is set */
}

说明: 诀窍是基于类似可变函数的宏 (...) 和预处理器令牌连接 (##)。

  1. is_set 只是一个方便其参数扩展的包装器。
  2. is_set_ 尝试连接 macrotest_ 与其输入的评估值 (comma)。 如果定义了它的输入,那么它就可以工作;否则 is_set__macrotest_<macro> 调用,其中 <macro>is_set 的原始参数(例如,如果 foo 不是已定义的宏,则 is_set(foo) 会导致 macrotest_foo)。

    李>
  3. is_set__ 中,它的参数再次被扩展,但这只有在传递macrotest_1 时才有效。 如果是,则调用is_set___(, 1, 0),因为comma 的计算结果为,(注意3 个参数!)。 对于逗号的任何其他值(即,如果要测试的宏未定义或具有除1 之外的任何其他(扩展)值,则参数无法扩展,因此会调用 is_set___(macrotest_<macro> 1, 0),它只有 2 个参数。

  4. 最终,is_set___ 只需为其“输出”选择第二个参数,并丢弃其他所有参数。 由于is_set__ 的行为,如果定义了要测试的宏,则导致1 和1,否则导致0

【讨论】:

  • 引用的链接不起作用(不再)。这是依赖于外部链接而不至少引用相关部分的问题和答案的普遍问题。请注意,这违反了网站规则和有效的反对理由。
  • @Olaf,该链接对我有用。它看起来像是 G+ 上用户个人资料的有效链接:https://plus.google.com/u/{id}/{id}/posts
  • 是的,该链接指向提出这个想法的人的 G+ 个人资料。
  • 所以它与浏览器或地理锁定有些不兼容。不管是什么,这都违反了(当前的)站点规则。答案必须是独立的。
  • 答案自成一体。
【解决方案2】:

好的,根据之前的帖子,我得到了这个想法,这似乎可行:

#define DEFINEDX(NAME) ((#NAME)[0] == 0) #define DEFINED(NAME) DEFINEDX(NAME)

这将检查 NAME 是否已定义,因此它将扩展为第一个字符为 0 的空字符串,或者它未定义,在这种情况下它不是空字符串。这适用于 GCC,因此可以编写

如果(定义(宏)) ...

【讨论】:

  • 请记住,尽管这使用了 C 语句,并且只会在预编译器中进行部分扩展。据我了解,C 编译器随后意识到条件的结果是静态的并消除了未使用的代码......
  • 很酷,但它只适用于空宏...也就是说,如果你执行#define M y,那么 DEFINED(M) 将返回 0(用 gcc 4.5.1 测试)
【解决方案3】:

您为什么不简单地根据该宏定义 ASSERT

#ifdef MACRO
#define ASSERT(NAME, TEST) \
    do { \
        printf("Assert failed"); \
    } while(0)
#else
#define ASSERT(NAME, TEST) {}
#endif

应避免在 C 条件语句中使用固定的预处理器值 - 确保编译器应该优化死代码,但是当您可以从本质上删除实际的 C 代码时,为什么还要依赖它呢?

编辑:

有一个相当的技巧涉及宏参数字符串化,您可能可以使用:

#include <string.h>
#include <stdio.h>

#define X

#define ERROR_(NAME, TEXT) \
        if (strcmp("", #NAME) == 0) \
                printf("%s\n", TEXT)
#define ERROR(n, t) ERROR_(n, t)

int main() {
    ERROR(X, "Error: X");
    ERROR(Y, "Error: Y");

    return 0;
}

这个输出:

$ ./test
Error: X

本质上,它使用的事实是,当预处理器标记定义为宏时,它会扩展为自身。另一方面,当它 is 定义时,它会扩展为空字符串或其定义。除非您的某个宏有自己的名称作为定义,否则这个 hack 应该可以工作。

免责声明:使用此功能需您自担风险!

(...因为我肯定不会使用它!)

编辑 2:

上面程序的gcc -O0 -S的汇编输出是:

        .file   "test.c"
        .section        .rodata
.LC0:
        .string "Error: X"
        .text
.globl main
        .type   main, @function
main:
.LFB0:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        movq    %rsp, %rbp
        .cfi_offset 6, -16
        .cfi_def_cfa_register 6
        movl    $.LC0, %edi
        call    puts
        movl    $0, %eax
        leave
        ret
        .cfi_endproc
.LFE0:
        .size   main, .-main
        .ident  "GCC: (GNU) 4.4.3"
        .section        .note.GNU-stack,"",@progbits

即使没有优化,GCC 也会将此程序简化为单个 puts() 调用。该程序产生完全相同的汇编输出:

#include <stdio.h>

int main() {
    puts("Error: X");

    return 0;
}

因此,您可能不会有任何性能问题,具体取决于您的编译器和任何优化...

【讨论】:

  • 不,这样不好。我一直想定义 ASSERT,但如果 NAME 未定义,那么我不想做任何事情。另外,我不想在每次使用 ASSERT 时都加上 #ifdef
  • @Miklos Maroti:所以 NAME 的定义不是全局的,而 ASSERT 是全局的,您希望 ASSERT 仅在定义 NAME 时起作用?
  • 我想写这样的代码:ASSERT(UART, var >= 0);断言(SPI,var == 0);所以如果我定义了UART,那么所有的uart断言都应该打开,如果我定义了SPI,那么所有的spi断言都应该打开,并且我不想要预定义的固定数量的子系统,即ASSERT_UART,ASSERT_SPI不是一个好的解决方案.
  • @Miklos Maroti:我想 always 将这些宏定义为 0 或 1 也不是你想要的?
  • 是的,我不想定义这些,所以它们都被禁用了。
猜你喜欢
  • 2017-01-10
  • 1970-01-01
  • 2018-02-09
  • 2014-10-15
  • 2013-07-12
  • 1970-01-01
  • 2015-09-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多