Jak porównać ciągi w warunkowych dyrektywach preprocesora języka C.


92

Muszę zrobić coś takiego w C. Działa tylko wtedy, gdy używam znaku, ale potrzebuję ciągu. Jak mogę to zrobić?

#define USER "jack" // jack or queen

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

Dlaczego nie możesz po prostu użyć strcmp?

@Brian: Tak, też przeczytałem pytanie :-). Chciałem się tylko upewnić, że wie, że strcmp istnieje, a odpowiedź może być pouczająca, ponieważ nie mogę wymyślić powodu, aby zrobić to #define.

2
Chciałem tylko wspomnieć, że to samo dotyczy zwykłego kodu, a nie tylko preprocesorów. Nigdy nie używaj łańcucha, gdy wystarczy prosta wartość. Łańcuchy mają znacznie większy narzut niż liczby całkowite lub wyliczenia i jeśli nie musisz robić nic więcej niż je porównywać, to łańcuchy są złym rozwiązaniem.
swestrup

Przydałoby się, gdyby pytanie zawierało nieco więcej informacji na temat pożądanego i rzeczywistego zachowania.
Brent Bradburn

Odpowiedzi:


70

Nie sądzę, aby w dyrektywach preprocesora można było całkowicie porównać łańcuchy o zmiennej długości. Możesz jednak wykonać następujące czynności:

#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

Możesz też trochę zmienić kod i zamiast tego użyć kodu C.


3
Albo mógł #define USER_VS (3 - USER)w tym konkretnym przypadku. :)
Jesse Chisholm

17

[AKTUALIZACJA: 2018.05.03]

PRZESTROGA : Nie wszystkie kompilatory implementują specyfikację C ++ 11 w ten sam sposób. Poniższy kod działa w kompilatorze, na którym testowałem, podczas gdy wielu komentujących używa innego kompilatora.

Cytując z odpowiedzi Shafika Yaghmoura z: Obliczanie długości łańcucha C w czasie kompilacji. Czy to naprawdę constexpr?

Nie ma gwarancji, że wyrażenia stałe zostaną ocenione w czasie kompilacji, mamy tylko nienormatywny cytat z wersji roboczej standardu C ++, sekcja 5.19. Wyrażenia stałe, które mówią tak:

[...]> [Uwaga: wyrażenia stałe można oceniać podczas tłumaczenia. — uwaga końcowa]

To słowo canrobi wielką różnicę na świecie.

Tak więc YMMV na tej (lub dowolnej) odpowiedzi obejmującej constexpr, w zależności od interpretacji specyfikacji przez autora kompilatora.

[ZAKTUALIZOWANO 2016.01.31]

Ponieważ niektórym nie podobała się moja wcześniejsza odpowiedź, ponieważ pozwoliła uniknąć całego compile time string compareaspektu PO poprzez osiągnięcie celu bez potrzeby porównywania ciągów, oto odpowiedź bardziej szczegółowa.

Nie możesz! Nie w C98 ani C99. Nawet w C11. Żadna ilość manipulacji MAKRO tego nie zmieni.

Definicja const-expressionużyta w #iftagu nie zezwala na ciągi.

Pozwala na postacie, więc jeśli ograniczysz się do postaci, możesz użyć tego:

#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

Możesz! W C ++ 11. Jeśli zdefiniujesz funkcję pomocniczą czasu kompilacji dla porównania.

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

Ostatecznie będziesz musiał zmienić sposób, w jaki osiągasz swój cel, jakim jest wybranie ostatecznych wartości ciągów dla USERi USER_VS.

Nie możesz porównywać ciągów czasu kompilacji w C99, ale możesz wybierać ciągi w czasie kompilacji.

Jeśli naprawdę musisz dokonywać porównań czasu kompilacji, musisz zmienić na C ++ 11 lub nowsze warianty, które pozwalają na tę funkcję.

[ORYGINALNA ODPOWIEDŹ NASTĘPUJE]

Próbować:

#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

AKTUALIZACJA: Wklejanie tokenu ANSI jest czasami mniej niż oczywiste. ;-RE

Umieszczenie singla #przed makrem powoduje, że zostaje ono zamienione na ciąg zawierający jego wartość, a nie samą wartość.

Umieszczenie ##znaku podwójnego między dwoma tokenami powoduje ich połączenie w jeden token.

Tak więc makro USER_VSma rozszerzenie jack_VSlub queen_VS, w zależności od ustawienia USER.

Stringify makro S(...)używa makro zadnie więc wartość o nazwie makro zostanie przekształcona w ciąg. zamiast nazwy makra.

W ten sposób USER##_VSstaje się jack_VS(lub queen_VS), w zależności od tego, jak ustawisz USER.

Później, gdy makro stringify jest używane jako S(USER_VS)wartość USER_VS( jack_VSw tym przykładzie), jest przesyłane do kroku pośredniego, S_(jack_VS)który konwertuje jego wartość ( queen) na ciąg "queen".

Jeśli ustawisz USERna, queenwynikiem końcowym jest łańcuch "jack".

Aby uzyskać informacje o konkatenacji tokenów, zobacz: https://gcc.gnu.org/onlinedocs/cpp/Concatenation.html

Informacje na temat konwersji ciągów tokenu można znaleźć na stronie : https://gcc.gnu.org/onlinedocs/cpp/Stringification.html#Stringification

[ZAKTUALIZOWANO 2015.02.15, aby poprawić literówkę]


5
@JesseChisholm, czy sprawdziłeś swoją wersję C ++ 11? Nie mogę sprawić, by działało na GCC 4.8.1, 4.9.1, 5.3.0. Mówi {{brak operatora binarnego przed tokenem "("}} w {{#if 0 == c_strmp / * tutaj * / (USER, QUEEN)}}
Dmitriy Elisov

3
@JesseChisholm Więc udało mi się skompilować Twój przykład z C ++ 11, jeśli zmienię #if 0 == c_strcmp( USER, JACK )naconstexpr int comp1 = c_strcmp( USER, JACK ); #if 0 == comp1
Dmitriy Elisov

4
@JesseChisholm, hmm, nadal nie ma szczęścia. Każda zmienna constexpr jest równa zeru w. #ifTwój przykład działa tylko dlatego, że USER to JACK. Gdyby USER był QUEEN, to powiedziałoby USER IS QUEENiUSER_VS IS QUEEN
Dmitriy Elisov

9
Ta część tej odpowiedzi w języku c ++ 11 jest nieprawidłowa. Nie możesz wywoływać funkcji (nawet constexpr) z dyrektyw preprocesora.
interjay

8
Ta zdecydowanie błędna odpowiedź już wprowadziła w błąd kogoś, kto się do niej odniósł. Nie możesz wywołać funkcji constexpr z preprocesora; constexpr nie jest nawet rozpoznawane jako słowo kluczowe aż do fazy tłumaczenia 7. Wstępne przetwarzanie odbywa się w fazie tłumaczenia 4.
H Walters

10

Poniższe zadziałały dla mnie z clangiem. Umożliwia to, co pojawia się jako symboliczne porównanie wartości makra. #error xxx służy tylko do sprawdzenia, co naprawdę robi kompilator. Zastąpienie definicji cat przez #define cat (a, b) a ## b psuje wszystko.

#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

Nie jestem pewien, czy to było złe, genialne, czy jedno i drugie, ale właśnie tego szukałem - dziękuję! Jeszcze jedną pomocną sztuczką jest # zdefiniowanie makr xUSER_ zaczynając od 1. Następnie możesz dodać klauzulę #else na końcu swojej listy #elsif, aby wychwycić przypadki, w których USER jest przypadkowo ustawiony na coś, czego nie wiesz, jak sobie poradzić. (W przeciwnym razie, jeśli
policzysz

8

Użyj wartości numerycznych zamiast ciągów.

Na koniec, aby przekonwertować stałe JACK lub QUEEN na łańcuch, użyj operatorów stringize (i / lub tokenize).


2

Jak już wspomniano powyżej, preprocesor ISO-C11 nie obsługuje porównywania ciągów. Jednak problem przypisania makra z „wartością przeciwną” można rozwiązać za pomocą „wklejania tokenów” i „dostępu do tabeli”. Proste makro-rozwiązanie Concatenate / stringify Jessego zawodzi w gcc 5.4.0, ponieważ stringizacja jest wykonywana przed oceną konkatenacji (zgodnie z ISO C11). Można to jednak naprawić:

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

Pierwsza linia (makro P_()) dodaje jedno pośrednie, aby następny wiersz (makro VS()) zakończył konkatenację przed stringizacją (zobacz Dlaczego potrzebuję podwójnej warstwy pośredniej dla makr? ). Makra strunizacji ( S()i S_()) pochodzą od Jesse.

Tabela (makra jack_VSi queen_VS), która jest znacznie łatwiejsza do utrzymania niż konstrukcja if-then-else OP, pochodzi od Jessego.

Na koniec następny czteroliniowy blok wywołuje makra w stylu funkcji. Ostatni czteroliniowy blok pochodzi z odpowiedzi Jessego.

Przechowywanie kodu foo.ci wywoływanie preprocesora gcc -nostdinc -E foo.cdaje:

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



"jack"
"USER_VS"

Dane wyjściowe są zgodne z oczekiwaniami. Ostatnia linia pokazuje, że USER_VSmakro nie jest rozwijane przed stringizacją.


Działa to ładnie, dopóki nie spróbuję faktycznie porównać wygenerowanego ciągu, aby wykonać kompilację warunkową: #if (S(USER)=="jack")- Otrzymuję błąd preprocesora podczas używania "- error: invalid token at start of a preprocessor expression.
ysap

1

Jeśli twoje łańcuchy są stałymi czasowymi kompilacji (tak jak w twoim przypadku), możesz użyć następującej sztuczki:

#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

Kompilator może z wyprzedzeniem powiedzieć wynik strcmp i zastąpi strcmp jego wynikiem, dając w ten sposób #define, który można porównać z dyrektywami preprocesora. Nie wiem, czy jest jakakolwiek różnica między kompilatorami / zależność od opcji kompilatora, ale zadziałało to dla mnie na GCC 4.7.2.

EDYCJA: po dalszym badaniu wygląda na to, że jest to rozszerzenie łańcucha narzędzi, a nie rozszerzenie GCC, więc weź to pod uwagę ...


7
Z pewnością nie jest to standardowe C i nie widzę, jak by to działało z żadnym kompilatorem. Kompilator może czasami podawać wyniki wyrażeń (nawet wywołania funkcji, jeśli są wbudowane), ale nie informują o preprocesorze. Czy używasz $jakiegoś rozszerzenia preprocesora?
ugoren

3
Wygląda na to, że składnia '#if $ USER_JACK == 0' działa, przynajmniej z GNU C ++ używanym do budowania natywnego kodu Androida (JNI) ... Nie wiedziałem o tym, ale jest to bardzo przydatne, dziękuję za poinformowanie nas o to!
gregko

6
Wypróbowałem to w GCC 4.9.1 i nie wierzę, że zrobi to, co myślisz, że robi. Chociaż kod będzie się kompilował, nie da oczekiwanego wyniku. „$” jest traktowane jako nazwa zmiennej. Tak więc preprocesor szuka zmiennej '$ USER_JACK', nie znajdując jej i nadając jej domyślną wartość 0. W ten sposób zawsze będziesz mieć USER_VS zdefiniowany jako USER_QUEEN niezależnie od strcmp
Vitali

1

Odpowiedź Patricka i Jessego Chisholma skłoniła mnie do wykonania następujących czynności:

#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

Zamiast #define USER 'Q' #define USER QUEEN powinien również działać, ale nie został przetestowany działa również i może być łatwiejszy w obsłudze.

EDYCJA: Zgodnie z komentarzem @ Jean-François Fabre dostosowałem swoją odpowiedź.


zmiana (s==QUEEN?1:0)by (s==QUEEN)nie potrzebna trójskładnik, wynik jest już wartością logiczną
Jean-François Fabre

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

jest to w zasadzie statyczna tablica znaków o stałej długości, inicjowana ręcznie zamiast statycznej tablicy znaków o zmiennej długości, inicjalizowana automatycznie, zawsze kończąca się kończącym znakiem o wartości null


0

Nie możesz tego zrobić, jeśli USER jest zdefiniowany jako ciąg znaków w cudzysłowie.

Ale możesz to zrobić, jeśli USER to po prostu JACK lub QUEEN, Joker lub cokolwiek.

Istnieją dwie sztuczki do wykorzystania:

  1. Łączenie tokenów, w którym łączysz identyfikator z innym identyfikatorem, po prostu konkatenując ich znaki. Dzięki temu możesz porównać z JACK-em bez konieczności robienia #define JACKczegoś
  2. wariadyczne rozwijanie makr, które umożliwia obsługę makr ze zmienną liczbą argumentów. Pozwala to rozwinąć określone identyfikatory w różne liczby przecinków, które staną się porównaniem ciągów.

Zacznijmy więc od:

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

Teraz, jeśli napiszę JACK_QUEEN_OTHER(USER), a USER to JACK, preprocesor zamieni to wEXPANSION1(ReSeRvEd_, JACK, 1, 2, 3)

Krok drugi to konkatenacja:

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

Teraz JACK_QUEEN_OTHER(USER)staje sięEXPANSION2(ReSeRvEd_JACK, 1, 2, 3)

Daje to możliwość dodania liczby przecinków w zależności od tego, czy ciąg pasuje, czy nie:

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

Jeśli USER to JACK, zmienia JACK_QUEEN_OTHER(USER)się naEXPANSION2(x,x,x, 1, 2, 3)

Jeśli USER to QUEEN, zmienia JACK_QUEEN_OTHER(USER)się naEXPANSION2(x,x, 1, 2, 3)

Jeśli USER to inny, JACK_QUEEN_OTHER(USER)staje sięEXPANSION2(ReSeRvEd_other, 1, 2, 3)

W tym momencie wydarzyło się coś krytycznego: czwarty argument makra EXPANSION2 to 1, 2 lub 3, w zależności od tego, czy pierwotnym przekazanym argumentem była walet, dama, czy cokolwiek innego. Więc wszystko, co musimy zrobić, to go wyłowić. Z rozwlekłych powodów do ostatniego kroku będziemy potrzebować dwóch makr; będą to EXPANSION2 i EXPANSION3, nawet jeśli jeden z nich wydaje się niepotrzebny.

Łącząc to wszystko, mamy 6 makr:

#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

Możesz ich używać w ten sposób:

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
}

Obowiązkowy link godbolt: https://godbolt.org/z/8WGa19


-5

To proste, myślę, że możesz po prostu powiedzieć

#define NAME JACK    
#if NAME == queen 
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.