Wie man Strings in bedingten C-Präprozessordirektiven vergleicht

Lesezeit: 14 Minuten

Benutzeravatar von frx08
frx08

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

  • Warum kannst du nicht einfach strcmp verwenden?

    Aryabhatta

    25. Februar 2010 um 17:07 Uhr

  • @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 tun.

    Aryabhatta

    25. Februar 2010 um 17:40 Uhr

  • Ich wollte nur erwähnen, dass das Gleiche auch für normalen Code gilt, nicht nur für Präprozessoren. Verwenden Sie niemals einen String, wenn ein einfacher Wert ausreicht. Strings haben viel mehr Overhead als Integer oder Enums und wenn Sie nichts weiter tun müssen, als sie zu vergleichen, dann sind Strings die falsche Lösung.

    – swestrup

    25. Februar 2010 um 17:45 Uhr

  • Es wäre praktisch, wenn die Frage etwas mehr Informationen über das gewünschte vs. tatsächliche Verhalten enthalten würde.

    – Brent Bradburn

    31. Januar 2014 um 4:19 Uhr

Ich glaube nicht, dass es eine Möglichkeit gibt, Zeichenfolgenvergleiche mit variabler Länge vollständig in Präprozessordirektiven durchzuführen. Du könntest aber vielleicht folgendes machen:

#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önnten den Code ein wenig umgestalten und stattdessen C-Code verwenden.

  • Oder er könnte #define USER_VS (3 - USER) in diesem speziellen Fall. 🙂

    – Jesse Chisholm

    31. Januar 2016 um 14:52 Uhr

Benutzeravatar von Jesse Chisholm
Jesse Chisholm

[UPDATE: 2021.01.04]

Eine Sache, die sich geändert hat, seit ich dies 2014 zum ersten Mal gepostet habe, ist das Format von #pragma message.

Heutzutage sind die Eltern Pflicht!

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

Der Code von 2016 (mit Zeichen, nicht mit Zeichenfolgen) funktioniert jedoch weiterhin in VS2019.

Aber, wie @Artyer betont, die Version mit c_strcmp funktioniert in KEINEM modernen Compiler.

[UPDATE: 2018.05.03]

VORBEHALTHinweis: Nicht alle Compiler implementieren die C++11-Spezifikation auf die gleiche Weise. Der folgende Code funktioniert in dem Compiler, den ich getestet habe, während viele Kommentatoren einen anderen Compiler verwendet haben.

Zitieren aus der Antwort von Shafik Yaghmour unter: Berechnung der Länge einer C-Zeichenfolge zur Kompilierzeit. Ist das wirklich ein constexpr?

Es ist nicht garantiert, dass konstante Ausdrücke zur Kompilierzeit ausgewertet werden, wir haben nur ein nicht normatives Zitat aus dem Entwurf des C++-Standards Abschnitt 5.19 Konstante Ausdrücke, das dies besagt:

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

Dieses Wort can macht den Unterschied in der Welt.

Also, YMMV zu dieser (oder jeder anderen) Antwort constexprabhängig von der Interpretation der Spezifikation durch den Compiler-Autor.

[UPDATED 2016.01.31]

Da einige meine frühere Antwort nicht mochten, weil es vermieden das Ganze compile time string compare Aspekt des OP, indem das Ziel ohne Zeichenfolgenvergleiche erreicht wird. Hier ist eine detailliertere Antwort.

Du kannst nicht! Nicht in C98 oder C99. Auch nicht in C11. Keine noch so große MACRO-Manipulation wird dies ändern.

Die Definition von const-expression verwendet in der #if erlaubt 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

Du kannst! In C++11. Wenn Sie eine Kompilierzeit-Hilfsfunktion für den Vergleich definieren.

[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

Letztendlich müssen Sie also die Art und Weise ändern, wie Sie Ihr Ziel erreichen, die endgültigen Zeichenfolgenwerte auszuwählen USER und USER_VS.

Sie können in C99 keine Zeichenfolgenvergleiche zur Kompilierungszeit durchführen, aber Sie können zur Kompilierungszeit Zeichenfolgen auswählen.

Wenn Sie wirklich Time-Sting-Vergleiche kompilieren müssen, müssen Sie zu C++11 oder neueren Varianten wechseln, die diese Funktion zulassen.

[ORIGINAL ANSWER FOLLOWS]

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

Ein Single setzen # bevor ein Makro bewirkt, dass es in eine Zeichenfolge seines Werts geändert wird, anstatt in seinen bloßen Wert.

Doppelt setzen ## zwischen zwei Token bewirkt, dass sie zu einem einzigen Token verkettet werden.

Also das Makro USER_VS hat die Erweiterung jack_VS oder queen_VSje nach Einstellung USER.

Das besaiten Makro S(...) verwendet Makroindirektion, sodass der Wert des benannten Makros in eine Zeichenfolge konvertiert wird. anstelle des Namens des Makros.

Daher USER##_VS wird jack_VS (oder queen_VS), je nach Einstellung USER.

Später, wenn die besaiten Makro wird verwendet als S(USER_VS) der Wert von USER_VS (jack_VS in diesem Beispiel) wird an den Indirektionsschritt übergeben S_(jack_VS) was seinen Wert umwandelt (queen) in eine Zeichenfolge "queen".

Wenn Sie einstellen USER zu queen dann ist das Endergebnis der String "jack".

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

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

[UPDATED 2015.02.15 to correct a typo.]

  • @JesseChisholm, hast du deine C++11-Version überprüft? Ich kann es nicht auf GCC 4.8.1, 4.9.1, 5.3.0 zum Laufen bringen. Es heißt {{fehlender binärer Operator vor Token “(“}} auf {{#if 0 == c_strmp/*here*/( USER, QUEEN )}}

    – Dmitri Elisov

    20. Juli 2016 um 16:53 Uhr


  • @JesseChisholm Also habe ich es geschafft, Ihr C ++ 11-Beispiel zu kompilieren, wenn ich mich ändere #if 0 == c_strcmp( USER, JACK ) zu constexpr int comp1 = c_strcmp( USER, JACK ); #if 0 == comp1

    – Dmitri Elisov

    20. Juli 2016 um 17:12 Uhr


  • @JesseChisholm, hmm, immer noch kein Glück. Jede constexpr-Variable ist gleich Null in #if.Ihr Beispiel funktioniert nur, weil USER JACK ist. Wenn USER KÖNIGIN wäre, würde es sagen USER IS QUEEN und USER_VS IS QUEEN

    – Dmitri Elisov

    20. Juli 2016 um 19:31 Uhr

  • Dieser c ++ 11-Teil dieser Antwort ist falsch. Sie können keine Funktionen aufrufen (sogar constexpr) aus Präprozessordirektiven.

    – zwischenjay

    29. April 2018 um 12:49 Uhr

  • Diese völlig falsche Antwort hat bereits jemanden in die Irre geführt, der darauf verwiesen hat. Sie können eine constexpr-Funktion nicht vom Präprozessor aufrufen; constexpr wird erst in Übersetzungsphase 7 als Schlüsselwort erkannt. Die Vorverarbeitung erfolgt in Übersetzungsphase 4.

    – H Walters

    29. April 2018 um 17:09 Uhr

Benutzeravatar von Konstantin Bobrovskii
Konstantin Bobrowskij

Folgendes hat bei mir mit Clang funktioniert. Ermöglicht das, was als symbolischer Vergleich von Makrowerten erscheint. #fehler xxx ist nur zu sehen, was der Compiler wirklich tut. Ersetzen Katze Definition mit #define cat(a,b) a ## b macht Sachen kaputt.

#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

  • Ich bin mir nicht sicher, ob das 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 #define. Dann können Sie am Ende Ihrer #elsif-Liste eine #else-Klausel hinzufügen, um Fälle abzufangen, in denen USER versehentlich auf etwas gesetzt ist, von dem Sie nicht wissen, wie Sie damit umgehen sollen. (Wenn Sie andernfalls von 0 aus nummerieren, wird der Fall 0 zu Ihrem Catchall, da dies der standardmäßige numerische Wert des Präprozessors für undefinierte Symbole ist.)

    – Zuruf

    2. März 2018 um 3:02 Uhr


Verwenden Sie anstelle von Zeichenfolgen numerische Werte.

Um schließlich die Konstanten JACK oder QUEEN in einen String umzuwandeln, verwenden Sie die Operatoren stringize (und/oder tokenize).

hermannks Benutzeravatar
hermannk

Wie bereits oben erwähnt, tut der ISO-C11-Präprozessor dies nicht String-Vergleich unterstützen. Das Problem, ein Makro mit dem „Gegenwert“ zu belegen, lässt sich jedoch mit „Token Paste“ und „Table Access“ lösen. Jesses einfache concatenate/stringify-Makrolösung schlägt mit gcc 5.4.0 fehl, weil die Stringisierung erfolgt ist Vor die Auswertung der Verkettung (nach ISO C11). Kann aber 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)

Die erste Zeile (macro P_()) fügt eine Indirektion hinzu, um die nächste Zeile (macro VS()) Beenden Sie die Verkettung Vor die Stringisierung (siehe Warum brauche ich eine doppelte Indirektionsebene für Makros?). Die Stringisierungsmakros (S() und S_()) sind von Jesse.

Die Tabelle (Makros jack_VS und queen_VS), die viel einfacher zu warten ist als die if-then-else-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.

Speichern des Codes in foo.c und Aufrufen des Präprozessors gcc -nostdinc -E foo.c Erträge:

# 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 die USER_VS Makro ist nicht expandiert vor der Besaitung.

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

    – jap

    25. März 2020 um 14:46 Uhr

  • Ich musste wickeln #pragma message( ... ) um das Display anzurufen S(...) damit es kompiliert und Ihre Ergebnisse erhält.

    – Jesse Chisholm

    4. Januar 2021 um 16:26 Uhr

Ich weiß, dass dies technisch gesehen die Frage des OP nicht beantwortet, aber wenn ich mir die obigen Antworten ansehe, stelle ich fest (nach dem, was ich verstehen kann), dass es keine einfache Möglichkeit gibt, einen Zeichenfolgenvergleich im Präprozessor durchzuführen, ohne auf einige zurückzugreifen ” Tricks” oder andere Compiler-spezifische Magie. Als ich es für meine Situation überdachte, wurde mir klar, dass es in Wirklichkeit nur einen festen Satz von Zeichenfolgen geben würde, mit denen Sie vergleichen möchten / könnten, da der Präprozessor ohnehin statische Zeichenfolgen verwenden müsste. Es ist also eher eine stilistische Sache, mit einer “Zeichenfolge” in Ihrem Code vergleichen zu können. Also beschloss ich, Definitionen hinzuzufügen, die die Syntax wie eine Zeichenfolge hatten (beim Lesen), aber nur Definitionen für Ganzzahlen waren, wie es aussieht, wie einige andere Leute vorgeschlagen haben. Zum Beispiel:

#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

Jetzt geht es also nur noch darum, die Definitionen angemessen aufzustellen.

Als konkreteres Beispiel wollte ich ursprünglich den Zeichenfolgenvergleich durchführen, damit ich bei Verwendung der Cereal-Serialisierungsbibliothek einen Standardarchivtyp angeben konnte. In Cereal gibt es drei gültige Archivtypen: JSON, XML und Binary, und ich wollte, dass der Benutzer diese als String-Variable in CMake eingeben kann. Ich mache das immer noch möglich (und schränke auch die Variablen mit der CACHE STRINGS-Eigenschaft von CMake ein), konvertiere dann aber die Zeichenfolge in eine Ganzzahl, bevor ich sie als Compiler-Definition übergebe. (Ich entschuldige mich im Voraus, da ich weiß, dass dies CMake-zentriert ist und dass dies nicht Teil der ursprünglichen Frage war.)

Wenn ich CMake zur Automatisierung verwende, füge ich in der Datei CMakeLists.txt das folgende SetupCereal.cmake-Skript hinzu:

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

Ich habe dann einen begleitenden CerealArchive.hpp-Header erstellt, der so aussieht:

#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

Und dann sieht der Client-Code so aus:

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

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

Der Standard-Archivtyp kann dann vom Entwickler als CMake-String-Variable ausgewählt werden (natürlich gefolgt von einer Neukompilierung).

Obwohl diese Lösung technisch gesehen keine Zeichenfolgen vergleicht, verhält sie sich syntaktisch irgendwie gleich.

Ich denke auch, dass SetupCereal.cmake weiter verallgemeinert werden könnte, um das Setup in einer Funktion zu kapseln, sodass es in anderen Situationen verwendet werden könnte, in denen Sie ähnliche Arten von Definitionen definieren möchten.

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

    – jap

    25. März 2020 um 14:46 Uhr

  • Ich musste wickeln #pragma message( ... ) um das Display anzurufen S(...) damit es kompiliert und Ihre Ergebnisse erhält.

    – Jesse Chisholm

    4. Januar 2021 um 16:26 Uhr

Wenn Ihre Strings Kompilierzeitkonstanten sind (wie in Ihrem Fall), können Sie den folgenden Trick anwenden:

#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 von strcmp im Voraus mitteilen und ersetzt strcmp durch sein Ergebnis, wodurch Sie ein #define erhalten, das mit Präprozessordirektiven verglichen werden kann. Ich weiß nicht, ob es Unterschiede zwischen Compilern/Abhängigkeiten von Compileroptionen gibt, aber es hat bei mir unter GCC 4.7.2 funktioniert.

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

  • 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 mitteilen (sogar Funktionsaufrufe, wenn sie inline sind), aber nicht der Präprozessor. Ist Ihre Verwendung von $ eine Art Präprozessorerweiterung?

    – ugoren

    10. Februar 2013 um 9:11 Uhr

  • Es sieht so aus, als ob die ‘#if $USER_JACK == 0’-Syntax 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

    1. Juli 2013 um 15:31 Uhr

  • Ich habe dies auf GCC 4.9.1 ausprobiert und glaube nicht, dass dies das tun wird, was Sie denken. Der Code wird zwar kompiliert, liefert jedoch 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 haben Sie USER_VS immer als USER_QUEEN definiert, unabhängig von strcmp

    – Vitali

    5. September 2014 um 18:34 Uhr


1422140cookie-checkWie man Strings in bedingten C-Präprozessordirektiven vergleicht

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy