So vergleichen Sie Zeichenfolgen in C-bedingten Präprozessor-Direktiven

90

Ich muss so etwas in C machen. Es funktioniert nur, wenn ich ein Zeichen verwende, aber ich brauche einen String. Wie kann ich das machen?

#define USER "jack" // jack or queen

#if USER == "jack"
#define USER_VS "queen"
#elif USER == "queen"
#define USER_VS "jack"
#endif
frx08
quelle
Warum kannst du nicht einfach strcmp verwenden?
@Brian: Ja, ich habe die Frage auch gelesen :-). Ich wollte nur sicherstellen, dass er wusste, dass strcmp existiert, und die Antwort könnte aufschlussreich sein, da ich mir keinen Grund vorstellen kann, dieses # define-Zeug zu machen.
2
Ich wollte nur erwähnen, dass das Gleiche auch für regulären Code gilt, nicht nur für Präprozessoren. Verwenden Sie niemals eine Zeichenfolge, wenn ein einfacher Wert ausreicht. Zeichenfolgen haben viel mehr Overhead als Ganzzahlen oder Aufzählungen. Wenn Sie nichts weiter tun müssen, als sie zu vergleichen, sind Zeichenfolgen die falsche Lösung.
Swestrup
Es wäre praktisch, wenn die Frage etwas mehr Informationen über das gewünschte und das tatsächliche Verhalten enthalten würde.
Brent Bradburn

Antworten:

67

Ich glaube nicht, dass es eine Möglichkeit gibt, String-Vergleiche mit variabler Länge vollständig in Präprozessor-Direktiven durchzuführen. Sie könnten vielleicht Folgendes tun:

#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

Oder Sie können den Code ein wenig umgestalten und stattdessen C-Code verwenden.

Brian R. Bondy
quelle
3
Oder er könnte #define USER_VS (3 - USER)in diesem speziellen Fall. :)
Jesse Chisholm
16

[UPDATE: 2018.05.03]

CAVEAT : Nicht alle Compiler implementieren die C ++ 11-Spezifikation auf dieselbe Weise. Der folgende Code funktioniert in dem Compiler, auf dem ich getestet habe, während viele Kommentatoren einen anderen Compiler verwendeten.

Zitat aus Shafik Yaghmours Antwort unter: Berechnung der Länge eines C-Strings zur Kompilierungszeit. Ist das wirklich ein constexpr?

Es wird nicht garantiert, dass konstante Ausdrücke zur Kompilierungszeit ausgewertet werden. Wir haben nur ein nicht normatives Zitat aus dem Entwurf des C ++ - Standardabschnitts 5.19 Konstante Ausdrücke, das dies jedoch sagt:

[...]> [Hinweis: Konstante Ausdrücke können während der Übersetzung ausgewertet werden. - Endnote]

Dieses Wort canmacht den Unterschied in der Welt.

Also, YMMV zu dieser (oder einer beliebigen) Antwort constexpr, die abhängig von der Interpretation der Spezifikation durch den Compiler-Autor beinhaltet.

[AKTUALISIERT am 31.01.2016]

Da einige meine frühere Antwort nicht mochten, weil sie den gesamten Aspekt des OP vermieden hat,compile time string compare indem das Ziel erreicht wurde, ohne dass Zeichenfolgenvergleiche erforderlich waren, finden Sie hier eine detailliertere Antwort.

Das kannst du nicht! Nicht in C98 oder C99. Nicht einmal in C11. Keine MACRO-Manipulation wird dies ändern.

Die Definition von const-expressionverwendet in #iferlaubt keine Zeichenfolgen.

Es erlaubt Zeichen. Wenn Sie sich also auf Zeichen beschränken, können Sie Folgendes verwenden:

#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

Sie können! In C ++ 11. Wenn Sie eine Kompilierungszeit-Hilfsfunktion für den Vergleich definieren.

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

Letztendlich müssen Sie also die Art und Weise ändern, in der Sie Ihr Ziel erreichen, die endgültigen Zeichenfolgenwerte für USERund auszuwählen USER_VS.

In C99 können Sie keine Vergleiche zur Kompilierungszeit von Zeichenfolgen durchführen, aber Sie können die Auswahl der Zeichenfolgen zur Kompilierungszeit durchführen.

Wenn Sie wirklich Vergleiche zum Kompilieren von Zeitstichen durchführen müssen, müssen Sie zu C ++ 11 oder neueren Varianten wechseln, die diese Funktion zulassen.

[ORIGINAL ANTWORT FOLGT]

Versuchen:

#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: Das Einfügen von ANSI-Token ist manchmal weniger als offensichtlich. ;-D

Wenn Sie ein einzelnes #vor ein Makro stellen, wird es in eine Zeichenfolge mit seinem Wert anstelle seines bloßen Werts geändert.

Wenn Sie ##zwischen zwei Token ein Double setzen , werden diese zu einem einzigen Token verkettet.

Das Makro USER_VShat also die Erweiterung jack_VSoder queen_VS, je nachdem, wie Sie es einstellen USER.

Das Stringify- Makro S(...)verwendet die Makro-Indirektion, sodass der Wert des benannten Makros in eine Zeichenfolge konvertiert wird. anstelle des Namens des Makros.

So USER##_VSwird jack_VS(oder queen_VS), je nachdem wie Sie einstellen USER.

Wenn später das Stringify- Makro als S(USER_VS)Wert von USER_VS( jack_VSin diesem Beispiel) verwendet wird, wird es an den Indirektionsschritt übergeben S_(jack_VS), der seinen Wert ( queen) in einen String konvertiert "queen".

Wenn Sie festlegen USER, queenist das Endergebnis die Zeichenfolge "jack".

Informationen zur Token-Verkettung finden Sie unter: https://gcc.gnu.org/onlinedocs/cpp/Concatenation.html

Informationen zur Konvertierung von Token-Zeichenfolgen finden Sie unter: https://gcc.gnu.org/onlinedocs/cpp/Stringification.html#Stringification

[AKTUALISIERT am 15.02.2015, um einen Tippfehler zu korrigieren.]

Jesse Chisholm
quelle
5
@ JesseChisholm, hast du deine C ++ 11 Version überprüft? Ich kann es unter GCC 4.8.1, 4.9.1, 5.3.0 nicht zum Laufen bringen. Es heißt {{fehlender binärer Operator vor Token "("}} auf {{#if 0 == c_strmp / * hier * / (USER, QUEEN)}}
Dmitriy Elisov
3
@ JesseChisholm Also habe ich es geschafft, Ihr C ++ 11-Beispiel zu kompilieren, wenn ich #if 0 == c_strcmp( USER, JACK )zuconstexpr int comp1 = c_strcmp( USER, JACK ); #if 0 == comp1
Dmitriy Elisov
4
@ JesseChisholm, hmm, immer noch kein Glück. Jede constexpr-Variable ist in gleich Null. #ifIhr Beispiel funktioniert nur, weil USER JACK ist. Wenn USER QUEEN wäre, würde es sagen USER IS QUEENundUSER_VS IS QUEEN
Dmitriy Elisov
9
Dieser c ++ 11 Teil dieser Antwort ist falsch. Sie können keine Funktionen (auch nicht constexpr) aus Präprozessoranweisungen aufrufen .
Interjay
8
Diese geradezu falsche Antwort hat bereits jemanden in die Irre geführt, der darauf verwiesen hat. Sie können keine constexpr-Funktion vom Präprozessor aus aufrufen. constexpr wird erst in der Übersetzungsphase 7 als Schlüsselwort erkannt. Die Vorverarbeitung erfolgt in der Übersetzungsphase 4.
H Walters
9

Das Folgende hat bei mir mit Klirren funktioniert. Ermöglicht den symbolischen Makrowertvergleich. #error xxx ist nur um zu sehen, was der Compiler wirklich macht. Das Ersetzen der Katzendefinition durch #define cat (a, b) a ## b bricht die Dinge.

#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
Konstantin Bobrovskii
quelle
Ich bin mir nicht sicher, ob dies böse, brillant oder beides war, aber es war genau das, wonach ich gesucht habe - danke! Ein weiterer hilfreicher Trick besteht darin, Ihre xUSER_-Makros ab 1 zu definieren. Anschließend können Sie am Ende Ihrer # elsif-Liste eine # else-Klausel hinzufügen, um Fälle abzufangen, in denen USER versehentlich auf etwas eingestellt ist, mit dem Sie nicht umgehen können. (Andernfalls, wenn Sie von 0 nummerieren, wird der Fall 0 zu Ihrem Sammelpunkt, da dies der numerische Standardwert des Präprozessors für undefinierte Symbole ist.)
Sclamage
8

Verwenden Sie numerische Werte anstelle von Zeichenfolgen.

Verwenden Sie zum Konvertieren der Konstanten JACK oder QUEEN in eine Zeichenfolge die Operatoren stringize (und / oder tokenize).

Patrick
quelle
2

Wie bereits oben erwähnt, wird die ISO-C11 - Präprozessor nicht String - Vergleich unterstützen. Das Problem der Zuweisung eines Makros mit dem "entgegengesetzten Wert" kann jedoch mit "Token-Einfügen" und "Tabellenzugriff" gelöst werden. Jesses einfache Makro-Lösung zum Verketten / Stringifizieren schlägt mit gcc 5.4.0 fehl, da die Stringisierung vor der Auswertung der Verkettung erfolgt ( gemäß ISO C11). Es kann jedoch behoben werden:

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

In der ersten Zeile (Makro P_()) wird eine Indirektion hinzugefügt , damit die nächste Zeile (Makro VS()) die Verkettung vor der Stringisierung beendet (siehe Warum benötige ich für Makros eine doppelte Indirektionsebene? ). Die Stringisierungsmakros ( S()und S_()) stammen von Jesse.

Die Tabelle (Makros jack_VSund queen_VS), die viel einfacher zu pflegen ist als die Wenn-Dann-Sonst-Konstruktion des OP, stammt von Jesse.

Schließlich ruft der nächste vierzeilige Block die Makros im Funktionsstil auf. Der letzte vierzeilige Block stammt aus Jesses Antwort.

Das Speichern des Codes in foo.cund das Aufrufen des Präprozessors gcc -nostdinc -E foo.cergeben:

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



"jack"
"USER_VS"

Die Ausgabe ist wie erwartet. Die letzte Zeile zeigt, dass das USER_VSMakro vor der Stringisierung nicht erweitert wird.

hermannk
quelle
Dies funktioniert gut, bis ich versuche, die generierte Zeichenfolge tatsächlich zu vergleichen , um eine bedingte Kompilierung durchzuführen: #if (S(USER)=="jack")- Ich erhalte einen Präprozessorfehler, wenn ich "- verwende error: invalid token at start of a preprocessor expression.
Ysap
1

Wenn Ihre Zeichenfolgen (wie in Ihrem Fall) Kompilierungszeitkonstanten sind, können Sie den folgenden Trick verwenden:

#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

Der Compiler kann das Ergebnis des strcmp im Voraus mitteilen und ersetzt das strcmp durch das Ergebnis, sodass Sie eine #define erhalten, die mit Präprozessor-Direktiven verglichen werden kann. Ich weiß nicht, ob es Unterschiede zwischen Compilern / Abhängigkeit von Compileroptionen gibt, aber es hat bei GCC 4.7.2 für mich funktioniert.

BEARBEITEN: Nach weiteren Untersuchungen sieht es so aus, als wäre dies eine Toolchain-Erweiterung, keine GCC-Erweiterung. Berücksichtigen Sie dies also ...

EpsilonVector
quelle
7
Dies ist sicherlich kein Standard C, und ich sehe nicht, wie es mit einem Compiler funktionieren würde. Der Compiler kann manchmal die Ergebnisse von Ausdrücken (sogar Funktionsaufrufe, wenn sie inline sind), aber nicht den Vorprozessor erkennen. Verwenden Sie $eine Art Pre-Prozessor-Erweiterung?
Ugoren
3
Es sieht so aus, als ob die Syntax '#if $ USER_JACK == 0' funktioniert, zumindest mit GNU C ++, das zum Erstellen von nativem Android-Code (JNI) verwendet wird ... Ich wusste das nicht, aber es ist sehr nützlich, danke, dass Sie uns davon erzählt haben es!
Gregko
6
Ich habe dies in GCC 4.9.1 ausprobiert und glaube nicht, dass dies das tun wird, was Sie denken. Während der Code kompiliert wird, erhalten Sie nicht das erwartete Ergebnis. '$' wird als Variablenname behandelt. Der Präprozessor sucht also nach der Variablen '$ USER_JACK', findet sie nicht und gibt ihr den Standardwert 0. Daher ist USER_VS unabhängig von strcmp immer als USER_QUEEN definiert
Vitali
1

Die Antwort von Patrick und Jesse Chisholm brachte mich dazu, Folgendes zu tun:

#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

Anstatt #define USER 'Q' #define USER QUEEN sollte auch funktionieren, wurde aber nicht getestet funktioniert auch und ist möglicherweise einfacher zu handhaben.

EDIT: Nach dem Kommentar von @ Jean-François Fabre habe ich meine Antwort angepasst.

benni
quelle
Änderung (s==QUEEN?1:0)durch (s==QUEEN)Sie brauchen nicht das ternäre, Ergebnis ist bereits ein Boolescher Wert
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

Es handelt sich im Grunde genommen um ein statisches Zeichenarray mit fester Länge, das manuell initialisiert wird, anstelle eines statischen Zeichenarrays mit variabler Länge, das automatisch initialisiert wird und immer mit einem abschließenden Nullzeichen endet

Yan Bellavance
quelle
-5

Es ist einfach, denke ich, kann man einfach sagen

#define NAME JACK    
#if NAME == queen 
Pabitra Nayak
quelle