如何比较C条件预处理程序指令中的字符串

时间:2010-02-25 16:56:51

标签: c conditional c-preprocessor

我必须在C中做这样的事情。它只在我使用char时有效,但我需要一个字符串。我怎么能这样做?

#define USER "jack" // jack or queen

#if USER == "jack"
#define USER_VS "queen"
#elif USER == "queen"
#define USER_VS "jack"
#endif

11 个答案:

答案 0 :(得分:55)

我认为没有办法在预处理程序指令中完全进行可变长度字符串比较。您可以执行以下操作:

#define USER_JACK 1
#define USER_QUEEN 2

#define USER USER_JACK 

#if USER == USER_JACK
#define USER_VS USER_QUEEN
#elif USER == USER_QUEEN
#define USER_VS USER_JACK
#endif

或者您可以稍微重构代码并使用C代码。

答案 1 :(得分:19)

[更新:2018.05.03]

CAVEAT :并非所有编译器都以相同的方式实现C ++ 11规范。 下面的代码在我测试的编译器中工作,而许多评论者使用不同的编译器。

来自Shafik Yaghmour的回答:Computing length of a C string at compile time. Is this really a constexpr?

  

不保证在编译时计算常量表达式   时间,我们只有草案C ++标准的非规范性引用   5.19节这样说的常量表达式:

     
    

[...]> [注意:可以在期间评估常量表达式     翻译 - 结束说明]

  

这个词can在世界上有所不同。

所以,YMMV对这个(或任何)涉及constexpr的答案,取决于编译器作者对规范的解释。

[更新2016.01.31]

由于有些人不喜欢我之前的回答,因为它避免 OP的整个compile time string compare方面,通过完成目标而不需要字符串比较,这里有更多详细的答案。

你不能!不在C98或C99中。甚至没有在C11。没有多少MACRO操作会改变这一点。

const-expression中使用的#if定义不允许使用字符串。

它允许使用字符,因此如果您将自己限制为字符,则可以使用此字符:

#define JACK 'J'
#define QUEEN 'Q'

#define CHOICE JACK     // or QUEEN, your choice

#if 'J' == CHOICE
#define USER "jack"
#define USER_VS "queen"
#elif 'Q' == CHOICE
#define USER "queen"
#define USER_VS "jack"
#else
#define USER "anonymous1"
#define USER_VS "anonymous2"
#endif

#pragma message "USER    IS " USER
#pragma message "USER_VS IS " USER_VS

你可以!在C ++ 11中。如果为比较定义了编译时辅助函数。

// compares two strings in compile time constant fashion
constexpr int c_strcmp( char const* lhs, char const* rhs )
{
    return (('\0' == lhs[0]) && ('\0' == rhs[0])) ? 0
        :  (lhs[0] != rhs[0]) ? (lhs[0] - rhs[0])
        : c_strcmp( lhs+1, rhs+1 );
}
// some compilers may require ((int)lhs[0] - (int)rhs[0])

#define JACK "jack"
#define QUEEN "queen"

#define USER JACK       // or QUEEN, your choice

#if 0 == c_strcmp( USER, JACK )
#define USER_VS QUEEN
#elif 0 == c_strcmp( USER, QUEEN )
#define USER_VS JACK
#else
#define USER_VS "unknown"
#endif

#pragma message "USER    IS " USER
#pragma message "USER_VS IS " USER_VS

因此,最终,您必须改变实现为USERUSER_VS选择最终字符串值的目标的方式。

你不能在C99中进行编译时字符串比较,但是你可以选择字符串进行编译时。

如果您真的必须进行编译时间比较,那么您需要更改为允许该功能的C ++ 11或更新版本。

[原始答案]

尝试:

#define jack_VS queen
#define queen_VS jack

#define USER jack          // jack    or queen, your choice
#define USER_VS USER##_VS  // jack_VS or queen_VS

// stringify usage: S(USER) or S(USER_VS) when you need the string form.
#define S(U) S_(U)
#define S_(U) #U

更新: ANSI令牌粘贴有时不太明显。 ;-D

在宏之前放置一个#会导致它被更改为其值的字符串,而不是其裸值。

在两个令牌之间加上##会导致它们连接成一个令牌。

因此,宏USER_VS具有展开jack_VSqueen_VS,具体取决于您设置USER的方式。

stringify S(...)使用宏间接,因此命名宏的值将转换为字符串。而不是宏的名称。

因此USER##_VS变为jack_VS(或queen_VS),具体取决于您设置USER的方式。

稍后,当 stringify 宏用作S(USER_VS)时,USER_VS(此示例中为jack_VS)的值将传递到间接步骤{{ 1}}将其值(S_(jack_VS))转换为字符串queen

如果您将"queen"设置为USER,则最终结果为字符串queen

对于标记连接,请参阅:https://gcc.gnu.org/onlinedocs/cpp/Concatenation.html

有关令牌字符串转换,请参阅:https://gcc.gnu.org/onlinedocs/cpp/Stringification.html#Stringification

[更新2015.02.15纠正错字。]

答案 2 :(得分:7)

使用数值代替字符串。

最后要将常量JACK或QUEEN转换为字符串,请使用stringize(和/或tokenize)运算符。

答案 3 :(得分:3)

以下为clang工作了。允许显示为符号宏值比较。 #error xxx 只是为了看看编译器到底做了什么。用 #define cat(a,b)a ## b 替换 cat 定义会破坏事物。

#define cat(a,...) cat_impl(a, __VA_ARGS__)
#define cat_impl(a,...) a ## __VA_ARGS__

#define xUSER_jack 0
#define xUSER_queen 1
#define USER_VAL cat(xUSER_,USER)

#define USER jack // jack or queen

#if USER_VAL==xUSER_jack
  #error USER=jack
  #define USER_VS "queen"
#elif USER_VAL==xUSER_queen
  #error USER=queen
  #define USER_VS "jack"
#endif

答案 4 :(得分:1)

PatrickJesse Chisholm的答复让我做了以下事情:

#define QUEEN 'Q'
#define JACK 'J'

#define CHECK_QUEEN(s) (s==QUEEN)
#define CHECK_JACK(s) (s==JACK)

#define USER 'Q'

[... later on in code ...]

#if CHECK_QUEEN(USER)
  compile_queen_func();
#elif CHECK_JACK(USER)
  compile_jack_func();
#elif
#error "unknown user"
#endif

而不是#define USER 'Q' #define USER QUEEN 也应该可以工作但未经过测试也可以工作,可能更容易处理。

编辑:根据@ Jean-FrançoisFabre的评论,我调整了我的答案。

答案 5 :(得分:1)

如上所述,ISO-C11预处理器支持字符串比较。但是,分配具有“相反值”的宏的问题可以通过“令牌粘贴”和“表访问”来解决。 Jesse的简单连接/字符串化宏解决方案因gcc 5.4.0而失败,因为字符串化评估连接之前完成(符合ISO C11)。但是,它可以修复:

#define P_(user) user ## _VS
#define VS(user) P_ (user)
#define S(U) S_(U)
#define S_(U) #U

#define jack_VS  queen
#define queen_VS jack

S (VS (jack))
S (jack)
S (VS (queen))
S (queen)

#define USER jack          // jack    or queen, your choice
#define USER_VS USER##_VS  // jack_VS or queen_VS
S (USER)
S (USER_VS)

第一行(宏P_())添加一个间接让下一行(宏VS())完成字符串化之前的串联(参见Why do I need double layer of indirection for macros? )。字符串化宏(S()S_())来自Jesse。

表格(宏jack_VSqueen_VS)比OP的if-then-else结构更容易维护,来自Jesse。

最后,下一个四行块调用函数式宏。最后四行是来自Jesse的回答。

将代码存储在foo.c中并调用预处理器gcc -nostdinc -E foo.c会产生:

# 1 "foo.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "foo.c"
# 9 "foo.c"
"queen"
"jack"
"jack"
"queen"



"jack"
"USER_VS"

输出符合预期。最后一行显示在字符串化之前USER_VS已展开。

答案 6 :(得分:0)

如果您的字符串是编译时常量(如您的情况),您可以使用以下技巧:

#define USER_JACK strcmp(USER, "jack")
#define USER_QUEEN strcmp(USER, "queen")
#if $USER_JACK == 0
#define USER_VS USER_QUEEN
#elif USER_QUEEN == 0
#define USER_VS USER_JACK
#endif

编译器可以提前告诉strcmp的结果,并将strcmp替换为其结果,从而为您提供一个可以与预处理器指令进行比较的#define。我不知道编译器/依赖编译器选项之间是否存在任何差异,但它对GCC 4.7.2有用。

编辑:经过进一步调查,看起来这是一个工具链扩展,而不是GCC扩展,所以考虑到这一点...

答案 7 :(得分:0)

start = time.time()

基本上是固定长度的静态char数组,手动初始化 而不是自动初始化的可变长度静态char数组始终以终止的空char结尾

答案 8 :(得分:0)

如果将USER定义为带引号的字符串,则不能这样做。

但是如果USER只是JACK或QUEEN或Joker或其他任何东西,您可以这样做。

有两个技巧:

  1. 令牌拼合,您只需将其字符串联即可将一个标识符与另一个标识符结合在一起。这样,您就可以与JACK进行比较,而不必#define JACK进行某些操作
  2. 可变宏扩展,它允许您处理具有可变数量参数的宏。这样,您就可以将特定的标识符扩展为不同数量的逗号,这将成为您的字符串比较。

所以让我们开始:

#define JACK_QUEEN_OTHER(u) EXPANSION1(ReSeRvEd_, u, 1, 2, 3)

现在,如果我写JACK_QUEEN_OTHER(USER),并且USER是JACK,则预处理器 变成EXPANSION1(ReSeRvEd_, JACK, 1, 2, 3)

第二步是串联:

#define EXPANSION1(a, b, c, d, e) EXPANSION2(a##b, c, d, e)

现在JACK_QUEEN_OTHER(USER)变成EXPANSION2(ReSeRvEd_JACK, 1, 2, 3)

这使您有机会根据字符串是否匹配来添加多个逗号:

#define ReSeRvEd_JACK x,x,x
#define ReSeRvEd_QUEEN x,x

如果USER为JACK,则JACK_QUEEN_OTHER(USER)变为EXPANSION2(x,x,x, 1, 2, 3)

如果USER是QUEEN,则JACK_QUEEN_OTHER(USER)变为EXPANSION2(x,x, 1, 2, 3)

如果USER是其他用户,则JACK_QUEEN_OTHER(USER)变为EXPANSION2(ReSeRvEd_other, 1, 2, 3)

在这一点上,发生了一些关键的事情:EXPANSION2宏的第四个参数是1、2或3,具体取决于传递的原始参数是jack,queen还是其他。因此,我们要做的就是挑选出来。出于复杂的原因,最后一步需要两个宏;它们将是EXPANSION2和EXPANSION3,即使其中的一个似乎不必要。

将它们放在一起,我们有以下6个宏:

#define JACK_QUEEN_OTHER(u) EXPANSION1(ReSeRvEd_, u, 1, 2, 3)
#define EXPANSION1(a, b, c, d, e) EXPANSION2(a##b, c, d, e)
#define EXPANSION2(a, b, c, d, ...) EXPANSION3(a, b, c, d)
#define EXPANSION3(a, b, c, d, ...) d
#define ReSeRvEd_JACK x,x,x
#define ReSeRvEd_QUEEN x,x

您可能会这样使用它们:

int main() {
#if JACK_QUEEN_OTHER(USER) == 1
  printf("Hello, Jack!\n");
#endif
#if JACK_QUEEN_OTHER(USER) == 2
  printf("Hello, Queen!\n");
#endif
#if JACK_QUEEN_OTHER(USER) == 3
  printf("Hello, who are you?\n");
#endif
}

强制性的锚定链接:https://godbolt.org/z/8WGa19

答案 9 :(得分:0)

虽然预处理器对字符串的处理非常有限,但大多数编译器在编译时也对字符串了解很多。例如,这可以在编译时成功比较 __BASE_FILE____FILE__

const int zero_div_warning __attribute__((unused)) =
     42 / !strcmp(__FILE__ , __BASE_FILE__);

在 .c 文件中找到时使用 gcc -Wno-div-by-zero -Werr 编译成功,在 .h 文件中找到时失败(静态内联函数)

-Wno-div-by-zero 是 -Wall 的一部分

虽然这可能无法解决您的特定用例,但它确实为在编译时比较常量字符串提供了许多可能性。

答案 10 :(得分:-3)

这很简单我认为你可以说

#define NAME JACK    
#if NAME == queen