Как сравнивать строки в условных директивах препроцессора 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, и ответ может быть поучительным, так как я не могу придумать причины для этого #define.

2
Просто хотел упомянуть, что то же самое касается и обычного кода, а не только препроцессоров. Никогда не используйте строку, если подойдет простое значение. Строки имеют гораздо больше накладных расходов, чем целые числа или перечисления, и если вам не нужно ничего делать, кроме их сравнения, то строки - неправильное решение.
swestrup

Было бы удобно, если бы вопрос включал немного больше информации о желаемом и фактическом поведении.
Brent Bradburn

Ответы:


71

Я не думаю, что есть способ полностью выполнять сравнение строк переменной длины в директивах препроцессора. Возможно, вы могли бы сделать следующее:

#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)в этом конкретном случае. :)
Джесси Чизхолм

17

[ОБНОВЛЕНИЕ: 2018.05.03]

ПРЕДОСТЕРЕЖЕНИЕ : не все компиляторы реализуют спецификацию C ++ 11 одинаково. Приведенный ниже код работает в компиляторе, который я тестировал, в то время как многие комментаторы использовали другой компилятор.

Цитата из ответа Шафика Ягмура по адресу: Вычисление длины строки C во время компиляции. Это действительно constexpr?

Не гарантируется, что постоянные выражения будут вычислены во время компиляции, у нас есть только ненормативная цитата из проекта стандарта C ++, раздел 5.19. Постоянные выражения, в котором говорится следующее:

[...]> [Примечание: постоянные выражения можно оценивать во время перевода. — конец примечания]

Это слово canимеет значение в этом мире.

Итак, YMMV на этот (или любой) ответ constexpr, в зависимости от интерпретации спецификации компилятором авторами.

[ОБНОВЛЕНО 2016.01.31]

Поскольку некоторым не понравился мой предыдущий ответ, потому что он избегал всего compile time string compareаспекта OP, выполнив задачу без необходимости сравнения строк, вот более подробный ответ.

Вы не можете! Ни в C98, ни в C99. Даже в C11. Никакие манипуляции с МАКРОМ этого не изменят.

Определение 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_VSили queen_VS, в зависимости от того, как вы установите USER.

Stringify макрос S(...)использует макрос косвенность поэтому значение имени макроса преобразуется в строку. вместо имени макроса.

Таким образом USER##_VSстановится jack_VS(или queen_VS), в зависимости от того, как вы установите USER.

Позже, когда макрос stringify используется в S(USER_VS)качестве значения USER_VS( jack_VSв этом примере), передается на шаг косвенного S_(jack_VS)обращения, который преобразует его value ( 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 / * здесь * / (USER, QUEEN)}}
Дмитрий Елисов

3
@JesseChisholm Итак, мне удалось скомпилировать ваш пример C ++ 11, если я перейду #if 0 == c_strcmp( USER, JACK )наconstexpr int comp1 = c_strcmp( USER, JACK ); #if 0 == comp1
Дмитрий Елисов

4
@JesseChisholm, хм, все равно не повезло. Любая переменная constexpr равна нулю в. #ifВаш пример работает только потому, что USER - это JACK. Если бы ПОЛЬЗОВАТЕЛЬ был КОРОЛЕВОЙ, он бы сказал USER IS QUEENиUSER_VS IS QUEEN
Дмитрий Елисов

9
Эта часть ответа на С ++ 11 неверна. Вы не можете вызывать функции (даже constexpr) из директив препроцессора.
Interjay

8
Этот категорический неправильный ответ уже ввел в заблуждение того, кто на него ссылался. Вы не можете вызвать функцию constexpr из препроцессора; constexpr даже не распознается как ключевое слово до этапа 7 перевода. Предварительная обработка выполняется на этапе 4 перевода.
H Walters

10

Следующее сработало для меня с лязгом. Разрешает то, что выглядит как сравнение символьных макросов. #error xxx - просто посмотреть, что на самом деле делает компилятор. Замена определения cat на #define cat (a, b) a ## b ломает вещи.

#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

Не уверен, было ли это злым, блестящим или и тем, и другим, но это было именно то, что я искал - спасибо! Еще один полезный трюк - # определить свои макросы xUSER_, начиная с 1. Затем вы можете добавить предложение #else в конец вашего списка #elsif, чтобы отловить случаи, когда для USER случайно установлено значение, с которым вы не знаете, как справиться. (В противном случае, если вы нумеруете от 0, то регистр 0 становится вашим основным, потому что это числовое значение препроцессора по умолчанию для неопределенных символов.)
sclamage

8

Используйте числовые значения вместо строк.

Наконец, чтобы преобразовать константы JACK или QUEEN в строку, используйте операторы преобразования строки (и / или токенизации).


2

Как уже было сказано выше, препроцессор ISO-C11 не поддерживает сравнение строк. Однако проблема присвоения макросу «противоположного значения» может быть решена с помощью «вставки токена» и «доступа к таблице». Простое макрорешение для конкатенации / строкового преобразования Джесси терпит неудачу с 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()) завершить конкатенацию перед преобразованием в строку (см. Зачем мне нужен двойной уровень косвенности для макросов? ). Макросы стрингизации ( S()и S_()) от Джесси.

Таблица (макросы jack_VSи queen_VS), которую намного проще поддерживать, чем конструкция оператора if-then-else, принадлежит Джесси.

Наконец, следующий четырехстрочный блок вызывает макросы функционального стиля. Последний четырехстрочный блок взят из ответа Джесси.

Сохранение кода 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' работает, по крайней мере, с GNU C ++, используемым для создания собственного кода Android (JNI) ... Я не знал этого, но это очень полезно, спасибо, что рассказали нам о Это!
gregko 01

6
Я пробовал это на GCC 4.9.1, и я не верю, что это будет делать то, что вы думаете. Пока код будет компилироваться, он не даст ожидаемого результата. '$' рассматривается как имя переменной. Таким образом, препроцессор ищет переменную '$ USER_JACK', не находя ее и не присваивая ей значение по умолчанию 0. Таким образом, вы всегда будете определять USER_VS как USER_QUEEN независимо от strcmp
Виталий

1

Ответ Патрика и Джесси Чизхолма заставил меня сделать следующее:

#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çois Fabre, я адаптировал свой ответ.


изменение (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

это в основном статический массив символов фиксированной длины, инициализированный вручную вместо статического массива символов переменной длины, инициализируемый автоматически, всегда заканчивающийся завершающим нулевым символом


0

Вы не можете этого сделать, если USER определен как строка в кавычках.

Но вы можете это сделать, если ПОЛЬЗОВАТЕЛЬ - это просто ДЖЕК, КОРОЛЕВА, Джокер или что-то еще.

Есть два приема:

  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

Если ПОЛЬЗОВАТЕЛЬ - ДЖЕК, JACK_QUEEN_OTHER(USER)становитсяEXPANSION2(x,x,x, 1, 2, 3)

Если ПОЛЬЗОВАТЕЛЬ - КОРОЛЕВА, 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, в зависимости от того, был ли передан исходный аргумент валетом, дамой или чем-то еще. Так что все, что нам нужно сделать, это выбрать его. По многим причинам нам понадобятся два макроса для последнего шага; это будут 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


Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.