测试是否在宏内部定义了预处理程序符号

时间:2014-09-29 12:17:28

标签: c macros c-preprocessor

测试是否定义了预处理程序符号的常用方法是使用#ifdef。但是,#ifdef不能在宏中使用。我需要的是一种检查宏的方法,如果该宏的参数是定义的预处理器符号。

例如:

#define TRACE(x,y) if(IS_DEFINED(x)){ std::cout << y; }

这里,TRACE有两个参数,第一个x应该是预处理器符号的名称。如果定义了这样的符号,则应打印第二个参数。我现在正在寻找不存在的IS_DEFINED函数/宏。

用法如下:

#undef BLA
TRACE(BLA,"abc") // "abc" won't be printed, as BLA is not defined
#define BLA 1
TRACE(BLA,"xyz") // "xyz" will be printed, as BLA is a defined symbol

有没有办法实现这个目标?也许是一些宏观魔法?当然,这个解决方案应该适用于任何符号,不仅仅是BLA或一组硬编码的符号。如果要检查的符号集是事先知道的话,这显然很容易。

5 个答案:

答案 0 :(得分:5)

将字符串化的宏(名称)与宏的字符串化(扩展)值进行比较:

#include <iostream>
#include <cstring>

#define TRACE_STRINGIFY(item) "" #item
#define TRACE(macro, message)                          \
    do {                                               \
        if (strcmp("" #macro, TRACE_STRINGIFY(macro))) \
            std::cout << message << "\n";              \
    } while (0)

"" # macro扩展为宏名称作为字符串,而TRACE_STRINGIFY(macro)首先展开宏,然后将结果字符串化。如果两者不同,macro必须是预处理器宏。

这种方法对于自己定义的宏(即#define FOO FOO)失败了。此类宏未被检测为预处理器宏。

大多数编译器应该能够完全优化两个字符串文字的比较。 GNU GCC(g ++)4.8.2肯定与-O0一样(对于C的gcc也是如此 - 同样的方法显然也适用于C)。

这种方法适用于类似函数的宏,但只有保留括号(以及适当数量的逗号,如果宏采用多个参数)并且它没有定义为自身(例如#define BAR(x) BAR(x))。

例如:

#define TEST1 TEST1

#define TEST3
#define TEST4 0
#define TEST5 1
#define TEST6 "string"
#define TEST7 ""
#define TEST8 NULL
#define TEST9 TEST3
#define TEST10 TEST2
#define TEST11(x)

#define TEST13(x,y,z) (x, y, z)


int main(void)
{
    TRACE(TEST1, "TEST1 is defined");
    TRACE(TEST2, "TEST2 is defined");
    TRACE(TEST3, "TEST3 is defined");
    TRACE(TEST4, "TEST4 is defined");
    TRACE(TEST5, "TEST5 is defined");
    TRACE(TEST6, "TEST6 is defined");
    TRACE(TEST7, "TEST7 is defined");
    TRACE(TEST8, "TEST8 is defined");
    TRACE(TEST9, "TEST9 is defined");
    TRACE(TEST10, "TEST10 is defined");
    TRACE(TEST11, "TEST11 is defined");
    TRACE(TEST12, "TEST12 is defined");
    TRACE(TEST13, "TEST13 is defined");
    TRACE(TEST14, "TEST14 is defined");

    TRACE(TEST1(), "TEST1() is defined");
    TRACE(TEST2(), "TEST2() is defined");
    TRACE(TEST3(), "TEST3() is defined");
    TRACE(TEST4(), "TEST4() is defined");
    TRACE(TEST5(), "TEST5() is defined");
    TRACE(TEST6(), "TEST6() is defined");
    TRACE(TEST7(), "TEST7() is defined");
    TRACE(TEST8(), "TEST8() is defined");
    TRACE(TEST9(), "TEST9() is defined");
    TRACE(TEST10(), "TEST10() is defined");
    TRACE(TEST11(), "TEST11() is defined");
    TRACE(TEST12(), "TEST12() is defined");
    TRACE(TEST13(,,), "TEST13(,,) is defined");
    TRACE(TEST14(,,), "TEST14(,,) is defined");

    return 0;
}

输出

TEST3 is defined
TEST4 is defined
TEST5 is defined
TEST6 is defined
TEST7 is defined
TEST8 is defined
TEST9 is defined
TEST10 is defined
TEST3() is defined
TEST4() is defined
TEST5() is defined
TEST6() is defined
TEST7() is defined
TEST8() is defined
TEST9() is defined
TEST10() is defined
TEST11() is defined
TEST13(,,) is defined

换句话说,TEST1符号未被识别为已定义(因为它被定义为自身),TEST11TEST13也没有括号。这些是这种方法的局限性。

使用带括号的表单适用于所有无参数宏(TEST1除外,即为自己定义的那些宏)以及所有单参数宏。如果宏需要多个参数,则需要使用正确数量的逗号,否则(例如,如果您尝试TRACE(TEST13(), "..."))会出现编译时错误:&#34; macro {{1需要3个参数,只有1个给定&#34; 或类似。

有问题吗?

答案 1 :(得分:4)

示例代码:

#include <iostream>

#define TRACE(name, msg) TRACE_EVAL_(TRACE_DO_, name, msg)
#define TRACE_EVAL_(macro, ...) macro(__VA_ARGS__)
#define TRACE_DO_(name, msg) \
    (#name[0] == 0 || #name[0] == '1' ? (void)(std::cout << (msg)) : (void)0)

#undef  FOO
#define BAR
#define BAZ 1

int main() {
    TRACE(FOO, "foo\n");
    TRACE(BAR, "bar\n");
    TRACE(BAZ, "baz\n");
}

根据宏定义的可能值,在TRACE_DO_中调整测试。请注意,支持非数字值的定义可能会有问题,因为很难从宏名称中识别它们......

答案 2 :(得分:2)

的Linux&#39; kgconfig.h为此用例定义了__is_defined宏:

 #define __ARG_PLACEHOLDER_1 0,
 #define __take_second_arg(__ignored, val, ...) val

/*
 * Helper macros to use CONFIG_ options in C/CPP expressions. Note that
 * these only work with boolean and tristate options.
 */

/*
 * Getting something that works in C and CPP for an arg that may or may
 * not be defined is tricky.  Here, if we have "#define CONFIG_BOOGER 1"
 * we match on the placeholder define, insert the "0," for arg1 and generate
 * the triplet (0, 1, 0).  Then the last step cherry picks the 2nd arg (a one).
 * When CONFIG_BOOGER is not defined, we generate a (... 1, 0) pair, and when
 * the last step cherry picks the 2nd arg, we get a zero.
 */
#define __is_defined(x)         ___is_defined(x)
#define ___is_defined(val)      ____is_defined(__ARG_PLACEHOLDER_##val)
#define ____is_defined(arg1_or_junk)    __take_second_arg(arg1_or_junk 1, 0)

它是C99并适用于三态选项(未定义,定义为0,定义为1)。

答案 3 :(得分:1)

如果您可以将BLA始终定义为01(或其他值可转换为bool),则可以

#define TRACE(x, y) if (x) { std::cout << y << std::endl; }

一个不错的编译器会优化if中的常量表达式,因此这种方法不会产生任何开销。

更新:可能未定义宏的代码:

#define IS_DEFINED(x) IS_DEFINED2(x)
#define IS_DEFINED2(x) (#x[0] == 0 || (#x[0] >= '1' && #x[0] <= '9'))
#define TRACE(x, y) if (IS_DEFINED(x)) { std::cout << y << std::endl; }

Demo

请注意,仅当FOO未定义,或定义为空或定义为某个数字时,它才有效。

工作原理
对于类似函数的宏,扩展的工作原理如下:首先扩展所有宏参数,除非它们与###一起使用,然后扩展宏本身(参见{{1}章节中的正式解释C ++标准,或C标准的16.3.1

因此6.10.3.1“使用展开的宏IS_DEFINED(x)调用”IS_DEFINED2(x)。如果定义了x,它将被替换为它定义的任何内容,否则它将按原样传递。

如果您直接致电x,则IS_DEFINED2(FOO)将始终等于#x,而这不是您想要的。

答案 4 :(得分:1)

基于Christoph的答案的另一个版本是我允许将可变参数传递给日志记录方法(在这种情况下是Objective-C但也适用于C ++)。

#define TGLog(tag, msg, ...) TGLog_eval_(TGLog_do_, tag, msg, ## __VA_ARGS__)
#define TGLog_eval_(macro, ...) macro(__VA_ARGS__)
#define TGLog_do_(tag, msg, ...) \
(#tag[0] == 0 || #tag[0] == '1') ? NSLog(@"%s",[NSString stringWithFormat:msg, ## __VA_ARGS__]) : (void)0;

如果根本没有参数,则在变量参数变量前面添加的##将删除前面的逗号。这可确保扩展不会因尾随逗号而失败。