如何在C条件预处理器指令中比较字符串


92

我必须在C中执行类似的操作。仅当使用char时才有效,但是我需要一个字符串。我怎样才能做到这一点?

#define USER "jack" // jack or queen

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

为什么不能只使用strcmp?

@Brian:是的,我也读过这个问题:-)。只是想确保他知道strcmp存在,并且响应可能会很有启发性,因为我想不出这样做的理由。

2
只是想提一下,常规代码也有同样的事情,而不仅仅是预处理程序。简单值可以使用时,切勿使用字符串。字符串比整数或枚举有更多的开销,如果您只需要比较它们就不需要做任何事情,那么字符串是错误的解决方案。
swestrup

如果问题中包含有关所需行为与实际行为的更多信息,这将很方便。
布伦特·布拉德本

Answers:


69

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

#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代码。


3
或者他可以#define USER_VS (3 - USER)在这种情况下。:)
Jesse Chisholm

17

[更新:2018.05.03]

CAVEAT:并非所有的编译器都以相同的方式实现C ++ 11规范。以下代码可在我测试过的编译器中使用,而许多注释者使用其他编译器。

引用Shafik Yaghmour的回答:在编译时计算C字符串的长度。这真的是constexpr吗?

不能保证在编译时对常量表达式进行求值,但是从C ++标准草案第5.19节常量表达式中我们只能得到一个非规范性的引用,尽管如此:

[...]> [注意:常量表达式可以在翻译过程中求值。

这个词can改变了世界。

因此,constexpr根据编译器作者对规范的解释,YMMV对此问题(或任何问题)的涉及。

[2016.01.31更新]

由于某些人不喜欢我之前的答案,因为它不需要字符串比较即可完成目标,从而避免compile time string compareOP的整个方面,因此这里提供了更详细的答案。

你不能!不在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

因此,最终,您将不得不改变实现为USER和选择最终字符串值的目标的方式USER_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

字符串化S(...)使用宏间接这样命名宏的值被转换成字符串。而不是宏的名称。

因此,USER##_VS成为jack_VS(或queen_VS),这取决于你如何设置USER

稍后,当将stringify宏用作(在本示例中)S(USER_VS)的值时,将传递到间接步骤,该步骤将其值()转换为string 。USER_VSjack_VSS_(jack_VS)queen"queen"

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

有关令牌串联的信息,请参见:https : //gcc.gnu.org/onlinedocs/cpp/Concatenation.html

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

[更新2015.02.15以纠正错字。]


5
@JesseChisholm,您检查过C ++ 11版本吗?我无法在GCC 4.8.1、4.9.1、5.3.0上运行。它说{{{#if 0 == c_strmp / * here * /(USER,QUEEN)}}上的{{在令牌“(”}}之前缺少二进制运算符
Dmitriy Elisov

3
@JesseChisholm因此,如果我更改#if 0 == c_strcmp( USER, JACK )constexpr int comp1 = c_strcmp( USER, JACK ); #if 0 == comp1
Dmitriy Elisov,

4
@JesseChisholm,嗯,还是没有运气。在您的#if示例中,任何constexpr变量都等于零。您的示例仅由于USER是JACK而起作用。若用户是QUEEN,它会说USER IS QUEENUSER_VS IS QUEEN
德米特里Elisov

9
这个答案的C ++ 11部分是错误的。您无法constexpr从预处理程序指令调用函数(甚至)。
interjay '18

8
这个完全错误的答案已经误导了引用它的人。您不能从预处理器调用constexpr函数;constexpr甚至没有被识别为关键字,直到翻译阶段7.预处理是在转换阶段4.完成
^ h沃尔特斯

9

以下对我有用。允许显示为符号宏值比较的内容。#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

不知道这是邪恶的,辉煌的还是两者兼而有之的,但这正是我在寻找的东西-谢谢!另一个有用的技巧是#定义从1开始的xUSER_宏。然后,您可以在#elsif列表的末尾添加#else子句,以捕获USER意外设置为您不知道如何处理的情况。(否则,如果您从0开始编号,那么0的情况将成为您的全部,因为这是未定义符号的预处理器的默认数值。)

8

使用数字值而不是字符串。

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


2

正如上面已经说过的,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)

第一行(macro P_())添加一个间接寻址,以使下一行(macro VS())在字符串化之前完成串联操作(请参阅 为什么我需要对宏进行双层间接寻址?)。字符串化宏(S()S_())来自Jesse。

该表(宏jack_VSqueen_VS)比Jesse的if-then-else构造容易维护。

最后,下一个四行代码块调用函数样式的宏。最后四行代码来自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宏在字符串化之前没有展开。


这很好,直到我尝试实际比较生成的字符串以进行条件编译之前:#if (S(USER)=="jack")-使用"-时出现预处理器错误error: invalid token at start of a preprocessor expression
ysap

1

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

#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
这当然不是标准的C语言,我不知道它如何与任何编译器一起使用。编译器有时可以告诉表达式的结果(即使是函数调用,如果它们是内联的),但不能告诉预处理器。您使用$某种预处理程序扩展吗?
ugoren

3
看起来“ #if $ USER_JACK == 0”语法有效,至少在用于构建本机Android代码(JNI)的GNU C ++上有效...我不知道这一点,但是它非常有用,感谢您告诉我们它!
gregko

6
我在GCC 4.9.1上进行了尝试,但我认为这不会像您想的那样工作。虽然代码可以编译,但不会给您预期的结果。'$'被视为变量名。因此,预处理程序正在寻找'$ USER_JACK'变量,而不是找到它并将其默认值设置为0。因此,无论strcmp
Vitali

1

由的answere帕特里克杰西·奇泽姆让我做到以下几点:

#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的评论,我修改了我的答案。


改变(s==QUEEN?1:0)(s==QUEEN)你不需要三元,结果已经是一个布尔
让·弗朗索瓦·法布尔

0
#define USER_IS(c0,c1,c2,c3,c4,c5,c6,c7,c8,c9)\
ch0==c0 && ch1==c1 && ch2==c2 && ch3==c3 && ch4==c4 && ch5==c5 && ch6==c6 && ch7==c7 ;

#define ch0 'j'
#define ch1 'a'
#define ch2 'c'
#define ch3 'k'

#if USER_IS('j','a','c','k',0,0,0,0)
#define USER_VS "queen"
#elif USER_IS('q','u','e','e','n',0,0,0)
#define USER_VS "jack"
#endif

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


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
}

强制性Godbolt链接:https ://godbolt.org/z/8WGa19


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.