Як порівняти рядки в умовних директивах препроцесора C


92

Я маю зробити щось подібне на C. Це працює, лише якщо я використовую символ, але мені потрібен рядок. Як я можу це зробити?

#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 stuff.

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

Було б зручно, якби питання містило трохи більше інформації про бажану та фактичну поведінку.
Brent Bradburn

Відповіді:


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 однаково. Наведений нижче код працює в компіляторі, на якому я тестував, тоді як багато коментаторів використовували інший компілятор.

Цитую відповідь Шафіка Ягмура за адресою: Обчислення довжини рядка С під час компіляції. Це справді constexpr?

Постійні вирази не гарантовано обчислюються під час компіляції, ми маємо лише ненормативну цитату з проекту стандарту C ++, розділ 5.19 Постійні вирази, де це сказано:

[...]> [Примітка: Постійні вирази можна обчислювати під час перекладу - кінцева примітка]

Це слово canмає велике значення у світі.

Отже, YMMV на цю (або будь-яку) відповідь включає constexpr, залежно від інтерпретації специфікації автором компілятора.

[ОНОВЛЕНО 2016.01.31]

Оскільки деяким не сподобалась моя попередня відповідь, оскільки вона уникнула всього compile time string compareаспекту ОП, досягнувши мети без необхідності порівняння рядків, ось більш детальна відповідь.

Ви не можете! Не в С98 чи С99. Навіть у 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_VSабо queen_VS, залежно від того, як ви встановили USER.

Stringify макрос S(...)використовує макрос опосередкованість тому значення імені макросу перетворюється в рядок. замість імені макросу.

Таким чином USER##_VSстає jack_VS(або queen_VS), залежно від того, як ви встановили USER.

Пізніше, коли макрос stringify використовується як S(USER_VS)значення USER_VS( jack_VSу цьому прикладі) передається на крок опосередкування, S_(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 / * тут * / (КОРИСТУВАЧ, КОРОЛЕВА)}}
Дмитро Елісов

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
Ця частина цієї відповіді на c ++ 11 є неправильною. Ви не можете викликати функції (навіть constexpr) із директив препроцесора.
інтержой

8
Ця беззаперечна неправильна відповідь уже вводила в оману когось, хто на неї посилався. Ви не можете викликати функцію constexpr з препроцесора; constexpr навіть не розпізнається як ключове слово до фази перекладу 7. Попередня обробка виконується на фазі перекладу 4.
Н Уолтерс

9

Наступне працювало для мене з дзвінкою. Дозволяє те, що виглядає як символічне порівняння значень макросів. #error xxx - просто побачити, що насправді робить компілятор. Заміна визначення кота на #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

Не впевнений, чи це було зло, блискуче чи те й інше, але це було саме те, що я шукав - дякую! Ще одним корисним прийомом є #define your xUSER_ macros, починаючи з 1. Потім ви можете додати речення #else в кінець вашого списку #elsif, щоб виявити випадки, коли USER випадково встановлений на щось, з чим ви не знаєте, як обробляти. (В іншому випадку, якщо ви нумеруєте від 0, тоді справа 0 стає вашим запитом, тому що це числове значення за замовчуванням препроцесора для невизначених символів.)
sclamage 02.03.18

8

Використовуйте числові значення замість рядків.

Нарешті, щоб перетворити константи JACK або QUEEN у рядок, використовуйте оператори stringize (та / або tokenize).


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), яку набагато простіше підтримувати, ніж побудова OP, якщо-то-інше, від Джессі.

Нарешті, наступний чотирирядковий блок викликає макроси у стилі функції. Останній блок із чотирьох рядків - з відповіді Джессі.

Зберігання коду 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, і я не бачу, як би це працювало з будь-яким компілятором. Компілятор може іноді повідомляти результати виразів (навіть виклики функцій, якщо вони вбудовані), але не попередній процесор. Ви використовуєте $якесь розширення попереднього процесора?
угорен

3
Схоже, синтаксис "#i $ $ USER_JACK == 0" працює, принаймні з GNU C ++, який використовується для створення власного коду Android (JNI) ... Я цього не знав, але це дуже корисно, дякую, що повідомили нам про це!
gregko

6
Я спробував це на GCC 4.9.1, і я не вірю, що це зробить те, що ви думаєте. Хоча код буде компілюватися, він не дасть вам очікуваного результату. '$' розглядається як ім'я змінної. Отже, препроцесор шукає змінну '$ USER_JACK', не знаходячи її та надаючи їй значення за замовчуванням 0. Таким чином, ви завжди будете мати USER_VS, визначений як USER_QUEEN, незалежно від strcmp
Vitali

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 також повинен працювати, але не тестувався також працює і може бути простішим в обробці.

EDIT: Відповідно до коментаря @ 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

Якщо USER - JACK, JACK_QUEEN_OTHER(USER)стаєEXPANSION2(x,x,x, 1, 2, 3)

Якщо USER - КОРОЛЕВА, 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


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.