开发者

How to compare strings in C conditional preprocessor-directives

开发者 https://www.devze.com 2022-12-21 10:58 出处:网络
I have to do something like this in C. It works only if I use a char, but I need a string. How can I do this?

I have to do something like this in C. It works only if I use a char, but I need a string. How can I do this?

#define USER "jack" // jack or queen

#if USER == "jack"开发者_StackOverflow社区
#define USER_VS "queen"
#elif USER == "queen"
#define USER_VS "jack"
#endif


I don't think there is a way to do variable length string comparisons completely in preprocessor directives. You could perhaps do the following though:

#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

Or you could refactor the code a little and use C code instead.


[UPDATE: 2021.01.04]

One thing that has changed since I first posted this in 2014, is the format of #pragma message.

Nowadays, the parens are required!

#pragma message ("USER    IS " USER)
#pragma message ("USER_VS IS " USER_VS)

That said, the 2016 code (using characters, not strings) still works in VS2019.

But, as @Artyer points out, the version involving c_strcmp will NOT work in ANY modern compiler.

[UPDATE: 2018.05.03]

CAVEAT: Not all compilers implement the C++11 specification in the same way. The below code works in the compiler I tested on, while many commenters used a different compiler.

Quoting from Shafik Yaghmour's answer at: Computing length of a C string at compile time. Is this really a constexpr?

Constant expressions are not guaranteed to be evaluated at compile time, we only have a non-normative quote from draft C++ standard section 5.19 Constant expressions that says this though:

[...]>[ Note: Constant expressions can be evaluated during translation.—end note ]

That word can makes all the difference in the world.

So, YMMV on this (or any) answer involving constexpr, depending on the compiler writer's interpretation of the spec.

[UPDATED 2016.01.31]

As some didn't like my earlier answer because it avoided the whole compile time string compare aspect of the OP by accomplishing the goal with no need for string compares, here is a more detailed answer.

You can't! Not in C98 or C99. Not even in C11. No amount of MACRO manipulation will change this.

The definition of const-expression used in the #if does not allow strings.

It does allow characters, so if you limit yourself to characters you might use this:

#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

You can! In C++11. If you define a compile time helper function for the comparison.

[2021.01.04: CAVEAT: This does not work in any MODERN compiler. See comment by @Artyer.]

// 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

So, ultimately, you will have to change the way you accomlish your goal of choosing final string values for USER and USER_VS.

You can't do compile time string compares in C99, but you can do compile time choosing of strings.

If you really must do compile time sting comparisons, then you need to change to C++11 or newer variants that allow that feature.

[ORIGINAL ANSWER FOLLOWS]

Try:

#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

UPDATE: ANSI token pasting is sometimes less than obvious. ;-D

Putting a single # before a macro causes it to be changed into a string of its value, instead of its bare value.

Putting a double ## between two tokens causes them to be concatenated into a single token.

So, the macro USER_VS has the expansion jack_VS or queen_VS, depending on how you set USER.

The stringify macro S(...) uses macro indirection so the value of the named macro gets converted into a string. instead of the name of the macro.

Thus USER##_VS becomes jack_VS (or queen_VS), depending on how you set USER.

Later, when the stringify macro is used as S(USER_VS) the value of USER_VS (jack_VS in this example) is passed to the indirection step S_(jack_VS) which converts its value (queen) into a string "queen".

If you set USER to queen then the final result is the string "jack".

For token concatenation, see: https://gcc.gnu.org/onlinedocs/cpp/Concatenation.html

For token string conversion, see: https://gcc.gnu.org/onlinedocs/cpp/Stringification.html#Stringification

[UPDATED 2015.02.15 to correct a typo.]


The following worked for me with clang. Allows what appears as symbolic macro value comparison. #error xxx is just to see what compiler really does. Replacing cat definition with #define cat(a,b) a ## b breaks things.

#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


Use numeric values instead of strings.

Finally to convert the constants JACK or QUEEN to a string, use the stringize (and/or tokenize) operators.


As already stated above, the ISO-C11 preprocessor does not support string comparison. However, the problem of assigning a macro with the “opposite value” can be solved with “token pasting” and “table access”. Jesse’s simple concatenate/stringify macro-solution fails with gcc 5.4.0 because the stringization is done before the evaluation of the concatenation (conforming to ISO C11). However, it can be fixed:

#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)

The first line (macro P_()) adds one indirection to let the next line (macro VS()) finish the concatenation before the stringization (see Why do I need double layer of indirection for macros?). The stringization macros (S() and S_()) are from Jesse.

The table (macros jack_VS and queen_VS) which is much easier to maintain than the if-then-else construction of the OP is from Jesse.

Finally, the next four-line block invokes the function-style macros. The last four-line block is from Jesse’s answer.

Storing the code in foo.c and invoking the preprocessor gcc -nostdinc -E foo.c yields:

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



"jack"
"USER_VS"

The output is as expected. The last line shows that the USER_VS macro is not expanded before stringization.


I know technically this isn't answering the OP's question, but in looking at the answers above, I am realizing (from what I can understand) that there isn't any easy way to do string comparison in the preprocessor without resorting to some "tricks" or other compiler specific magic. So in rethinking it for my situation, I realized that in reality there would only be a fixed set of strings that you would want to/could compare against, as the preprocessor would have to use static strings anyway. So it is more of a stylistic thing to be able to compare against a "string" like thing in your code. So I decided to add definitions that had the syntax like a string (when reading it) but were just defines for integers, which is what it looks like some other people have suggested. For example:

#if USER == USER_JACK
  // do something
#elif USER == USER_QUEEN
  // do something else
#elif USER == USER_KING
  // do something completely different
#else
  // abort abort
#end

So now it is just a question of setting up the definitions appropriately.

As a more concrete example, I originally wanted to do the string comparison so I could specify a default archive type when using the Cereal serialization library. In Cereal there are 3 valid Archive types: JSON, XML, and Binary, and I wanted the user to be able to input those as a string variable within CMake. I still make that possible (and also constrain the variables using CMake's CACHE STRINGS Property), but then convert the string to an integer before passing it as a compiler definition. (My apologies in advance as I know this is CMake-centric and that that wasn't part of the original question.)

Using CMake to automate things, in the CMakeLists.txt file, I include the following SetupCereal.cmake script:

set( CEREAL_DIR "" CACHE PATH "Path to Cereal installation" )
set( CEREAL_INCLUDE_DIR ${CEREAL_DIR}/include )

# Set up the  user input variable and constrain to valid values
set( CEREAL_ARCHIVE_DEFAULT_TYPE "JSON"
    CACHE STRING
    "Default Archive type to use for Cereal serialization"
)
set_property( CACHE CEREAL_ARCHIVE_DEFAULT_TYPE
    PROPERTY STRINGS JSON XML BINARY
)

# Convert the string to integer for preprocessor comparison
if ( "${CEREAL_ARCHIVE_DEFAULT_TYPE}" STREQUAL "JSON")
  set( CEREAL_ARCHIVE_DEFAULT_TYPE_VALUE 0 )
elseif( "${CEREAL_ARCHIVE_DEFAULT_TYPE}" STREQUAL "XML" )
  set( CEREAL_ARCHIVE_DEFAULT_TYPE_VALUE 1 )
elseif( "${CEREAL_ARCHIVE_DEFAULT_TYPE}" STREQUAL "BINARY" )
  set( CEREAL_ARCHIVE_DEFAULT_TYPE_VALUE 2 )
endif()

# Setup the corresponding preprocessor definitions
set( CEREAL_DEFINES
    -DCEREAL_ARCHIVE_JSON=0
    -DCEREAL_ARCHIVE_XML=1
    -DCEREAL_ARCHIVE_BINARY=2
    -DCEREAL_ARCHIVE_DEFAULT_TYPE=${CEREAL_ARCHIVE_DEFAULT_TYPE_VALUE}
)

I then made an accompanying CerealArchive.hpp header which looks like:

#pragma once

#if CEREAL_ARCHIVE_DEFAULT_TYPE == CEREAL_ARCHIVE_JSON
#  include <cereal/archives/json.hpp>

namespace cereal
{
  using DefaultOutputArchive = JSONOutputArchive;
  using DefaultInputArchive  = JSONInputArchive;
}

#elif CEREAL_ARCHIVE_DEFAULT_TYPE == CEREAL_ARCHIVE_XML
#  include <cereal/archives/xml.hpp>

namespace cereal {
  using DefaultOutputArchive = XMLOutputArchive;
  using DefaultInputArchive  = XMLInputArchive;
} // namespace cereal

#elif CEREAL_ARCHIVE_DEFAULT_TYPE == CEREAL_ARCHIVE_BINARY
#  include <cereal/archives/binary.hpp>

namespace cereal
{
  using DefaultOutputArchive = BinaryOutputArchive;
  using DefaultInputArchive  = BinaryInputArchive;
}

#endif // CEREAL_ARCHIVE_DEFAULT_TYPE

And then the client code looks like:

#include <CerealArchive.hpp>
#include <sstream>

std::ostringstream oss;
{
    cereal::DefaultOutputArchive archive( oss );
    archive( 123 );
}
std::string s = oss.str();

The default Archive type can then be selected by the developer as a CMake string variable (followed of course by a recompile).

So while technically this solution isn't comparing strings, syntactically it kind of behaves/looks the same.

I'm also thinking that the the SetupCereal.cmake could be further generalized to encapsulate the setup in a function, so it could be used in other situations where you want to define similar types of definitions.


If your strings are compile time constants (as in your case) you can use the following trick:

#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

The compiler can tell the result of the strcmp in advance and will replace the strcmp with its result, thus giving you a #define that can be compared with preprocessor directives. I don't know if there's any variance between compilers/dependance on compiler options, but it worked for me on GCC 4.7.2.

EDIT: upon further investigation, it look like this is a toolchain extension, not GCC extension, so take that into consideration...


You can't do that if USER is defined as a quoted string.

But you can do that if USER is just JACK or QUEEN or Joker or whatever.

There are two tricks to use:

  1. Token-splicing, where you combine an identifier with another identifier by just concatenating their characters. This allows you to compare against JACK without having to #define JACK to something
  2. variadic macro expansion, which allows you to handle macros with variable numbers of arguments. This allows you to expand specific identifiers into varying numbers of commas, which will become your string comparison.

So let's start out with:

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

Now, if I write JACK_QUEEN_OTHER(USER), and USER is JACK, the preprocessor turns that into EXPANSION1(ReSeRvEd_, JACK, 1, 2, 3)

Step two is concatenation:

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

Now JACK_QUEEN_OTHER(USER) becomes EXPANSION2(ReSeRvEd_JACK, 1, 2, 3)

This gives the opportunity to add a number of commas according to whether or not a string matches:

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

If USER is JACK, JACK_QUEEN_OTHER(USER) becomes EXPANSION2(x,x,x, 1, 2, 3)

If USER is QUEEN, JACK_QUEEN_OTHER(USER) becomes EXPANSION2(x,x, 1, 2, 3)

If USER is other, JACK_QUEEN_OTHER(USER) becomes EXPANSION2(ReSeRvEd_other, 1, 2, 3)

At this point, something critical has happened: the fourth argument to the EXPANSION2 macro is either 1, 2, or 3, depending on whether the original argument passed was jack, queen, or anything else. So all we have to do is pick it out. For long-winded reasons, we'll need two macros for the last step; they'll be EXPANSION2 and EXPANSION3, even though one seems unnecessary.

Putting it all together, we have these 6 macros:

#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

And you might use them like this:

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
}

Obligatory godbolt link: https://godbolt.org/z/8WGa19


MSVC Update: You have to parenthesize slightly differently to make things also work in MSVC. The EXPANSION* macros look like this:

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

Obligatory: https://godbolt.org/z/96Y8a1


The answere by Patrick and by Jesse Chisholm made me do the following:

#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

Instead of #define USER 'Q' #define USER QUEEN should also work but was not tested also works and might be easier to handle.

EDIT: According to the comment of @Jean-François Fabre I adapted my answer.


While the pre-processor is very limited with respect to strings, most compilers know a lot about strings at compile-time too. For instance this can successfully compare __BASE_FILE__ and __FILE__ at compile-time:

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

Compilation of this with gcc -Wno-div-by-zero -Werr succeeds when found in a .c file and fails when found in a .h file (static inline function)

-Wno-div-by-zero is part of -Wall

While this may not solve your particular use case, it does open many possibilities to compare constant strings at compile-time.


#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

it basically a fixed length static char array initialized manually instead of a variable length static char array initialized automatically always ending with a terminating null char


I've wanted to use string comparison as well in preprocessor macros, mostly so I can also "print" these values during preprocessing step (using pragma message).

As mentioned in accepted answer:

I don't think there is a way to do variable length string comparisons completely in preprocessor directives.

... however it seems doable, if the string value is written as an array of characters, both for the "variable"/tested macro, and for the macros that represent values to be tested against. For instance, I have tested the following code in https://replit.com/languages/c :

#include <stdio.h>

#define XSTR(x) STR(x)
#define STR(x) #x

#define TEST_STR ('t','e','s','t')
#define OTHER_STR ('o','t','h','e','r')
#define MYMACRO ('t','e','s','t')

#pragma message( "MYMACRO is: " XSTR(MYMACRO) )
#if MYMACRO==TEST_STR
#pragma message( "IS TRUE" )
#else
#pragma message( "IS FALSE" )
#endif

int main() {
    printf("Hello, world!\r\n");
    return 0;
}

... and it outputs:

> clang-7 -pthread -lm -o main main.c
main.c:10:9: warning: MYMACRO is: ('t','e','s','t')
      [-W#pragma-messages]
#pragma message( "MYMACRO is: " XSTR(MYMACRO) )
        ^
main.c:12:9: warning: IS TRUE [-W#pragma-messages]
#pragma message( "IS TRUE" )
        ^
2 warnings generated.
> ./main
Hello, world!
>  

... and if I change the condition to:

#if MYMACRO==OTHER_STR

... and recompile, the output is:

> clang-7 -pthread -lm -o main main.c
main.c:10:9: warning: MYMACRO is: ('t','e','s','t')
      [-W#pragma-messages]
#pragma message( "MYMACRO is: " XSTR(MYMACRO) )
        ^
main.c:14:9: warning: IS FALSE [-W#pragma-messages]
#pragma message( "IS FALSE" )
        ^
2 warnings generated.
> ./main
Hello, world!
> 

So, if the tested macro and the value macros are defined as lists of character values, then they can be compared directly in a preprocessor #if conditional - and in order to print such macros in a pragma message, use of stringifying macros are required.

Note that the commas and single quotation marks will remain visible, when printing macros like these in pragma message (I guess it might be possible to use variadic macros/__VA_ARGS__ to make a macro, that would loop through the character array values and concatenate them in a more readable manner, but the above technique is good enough for me).


#define USER "jack" // jack or queen
        
#ifdef USER \
  if (USER == "jack")#define USER_VS "queen" \
  else if(USER == "queen") #define USER_VS "jack"
#endif


It's simple I think you can just say

#define NAME JACK    
#if NAME == queen 
0

精彩评论

暂无评论...
验证码 换一张
取 消