Comment comparer des chaînes dans des directives de préprocesseur conditionnelles C


92

Je dois faire quelque chose comme ça en C. Cela ne fonctionne que si j'utilise un caractère, mais j'ai besoin d'une chaîne. Comment puis-je faire ceci?

#define USER "jack" // jack or queen

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

Pourquoi ne pouvez-vous pas simplement utiliser strcmp?

@Brian: Oui, j'ai lu la question aussi :-). Je voulais juste m'assurer qu'il savait que strcmp existe, et la réponse pourrait être éclairante, car je ne vois pas de raison de faire ce truc #define.

2
Je voulais juste mentionner que la même chose vaut pour le code normal, pas seulement pour les préprocesseurs. N'utilisez jamais de chaîne lorsqu'une valeur simple fera l'affaire. Les chaînes ont beaucoup plus de temps que les entiers ou les énumérations et si vous n'avez rien d'autre à faire que de les comparer, les chaînes ne sont pas la bonne solution.
swestrup

Il serait utile que la question contienne un peu plus d'informations sur le comportement souhaité par rapport au comportement réel.
Brent Bradburn

Réponses:


71

Je ne pense pas qu'il existe un moyen de faire des comparaisons de chaînes de longueur variable complètement dans les directives de préprocesseur. Vous pourriez peut-être faire ce qui suit:

#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

Ou vous pouvez refactoriser un peu le code et utiliser le code C à la place.


3
Ou il pourrait #define USER_VS (3 - USER)dans ce cas précis. :)
Jesse Chisholm

17

[MISE À JOUR: 2018.05.03]

CAVEAT : Tous les compilateurs n'implémentent pas la spécification C ++ 11 de la même manière. Le code ci-dessous fonctionne dans le compilateur sur lequel j'ai testé, tandis que de nombreux commentateurs utilisaient un compilateur différent.

Citant la réponse de Shafik Yaghmour à: Calcul de la longueur d'une chaîne C au moment de la compilation. Est-ce vraiment un constexpr?

Les expressions constantes ne sont pas garanties pour être évaluées au moment de la compilation, nous n'avons qu'une citation non normative du projet de norme C ++ section 5.19 Expressions constantes qui dit ceci cependant:

[...]> [Remarque: les expressions constantes peuvent être évaluées pendant la traduction. — note de fin]

Ce mot canfait toute la différence dans le monde.

Donc, YMMV sur cette (ou n'importe quelle) réponse impliquant constexpr, selon l'interprétation de l'auteur du compilateur de la spécification.

[MISE À JOUR 2016.01.31]

Comme certains n'aimaient pas ma réponse précédente car elle évitait tout l' compile time string compareaspect de l'OP en réalisant l'objectif sans avoir besoin de comparaisons de chaînes, voici une réponse plus détaillée.

Vous ne pouvez pas! Pas dans C98 ou C99. Pas même en C11. Aucune manipulation MACRO ne changera cela.

La définition de const-expressionutilisé dans #ifn'autorise pas les chaînes.

Il autorise les caractères, donc si vous vous limitez aux caractères, vous pouvez utiliser ceci:

#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

Vous pouvez! En C ++ 11. Si vous définissez une fonction d'aide à la compilation pour la comparaison.

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

Donc, en fin de compte, vous devrez changer la façon dont vous atteignez votre objectif de choisir les valeurs de chaîne finales pour USERet USER_VS.

Vous ne pouvez pas effectuer de comparaison de chaînes au moment de la compilation en C99, mais vous pouvez effectuer le choix des chaînes au moment de la compilation.

Si vous devez vraiment faire des comparaisons de temps de compilation, vous devez passer à C ++ 11 ou à des variantes plus récentes qui permettent cette fonctionnalité.

[RÉPONSE ORIGINALE SUIT]

Essayer:

#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

MISE À JOUR: le collage de jetons ANSI est parfois moins qu'évident. ;-RÉ

Mettre un single #avant une macro entraîne sa modification en une chaîne de sa valeur, au lieu de sa valeur nue.

Mettre un double ##entre deux jetons les fait être concaténés en un seul jeton.

Ainsi, la macro USER_VSa l'expansion jack_VSou queen_VS, selon la façon dont vous définissez USER.

La macro stringifyS(...) utilise l'indirection de macro pour que la valeur de la macro nommée soit convertie en chaîne. au lieu du nom de la macro.

Ainsi USER##_VSdevient jack_VS(ou queen_VS), selon la façon dont vous définissez USER.

Plus tard, lorsque la macro stringify est utilisée comme S(USER_VS)valeur de USER_VS( jack_VSdans cet exemple) est passée à l'étape d'indirection S_(jack_VS)qui convertit sa valeur ( queen) en chaîne "queen".

Si vous définissez USERsur, queenle résultat final est la chaîne "jack".

Pour la concaténation de jetons, voir: https://gcc.gnu.org/onlinedocs/cpp/Concatenation.html

Pour la conversion de chaîne de jeton, voir: https://gcc.gnu.org/onlinedocs/cpp/Stringification.html#Stringification

[MISE À JOUR 2015.02.15 pour corriger une faute de frappe.]


5
@JesseChisholm, avez-vous vérifié votre version C ++ 11? Je ne peux pas le faire fonctionner sur GCC 4.8.1, 4.9.1, 5.3.0. Il dit {{opérateur binaire manquant avant le jeton "("}} sur {{#if 0 == c_strmp / * here * / (USER, QUEEN)}}
Dmitriy Elisov

3
@JesseChisholm J'ai donc réussi à compiler votre exemple C ++ 11 si je change #if 0 == c_strcmp( USER, JACK )enconstexpr int comp1 = c_strcmp( USER, JACK ); #if 0 == comp1
Dmitriy Elisov

4
@JesseChisholm, hmm, toujours pas de chance. Toute variable constexpr est égale à zéro dans. #ifVotre exemple fonctionne uniquement parce que USER est JACK. Si USER était REINE, il dirait USER IS QUEENetUSER_VS IS QUEEN
Dmitriy Elisov

9
Cette partie c ++ 11 de cette réponse est fausse. Vous ne pouvez pas appeler de fonctions (même constexpr) à partir de directives de préprocesseur.
entre le

8
Cette mauvaise réponse a déjà induit en erreur quelqu'un qui l'a référencée. Vous ne pouvez pas appeler une fonction constexpr à partir du préprocesseur; constexpr n'est même pas reconnu comme mot-clé avant la phase de traduction 7. Le prétraitement est effectué dans la phase de traduction 4.
H Walters

10

Ce qui suit a fonctionné pour moi avec clang. Permet ce qui apparaît comme une comparaison de valeurs de macro symbolique. #error xxx est juste pour voir ce que fait vraiment le compilateur. Le remplacement de la définition de chat par #define cat (a, b) a ## b casse les choses.

#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

Je ne sais pas si c'était mal, brillant ou les deux, mais c'était exactement ce que je cherchais - merci! Une autre astuce utile consiste à # définir vos macros xUSER_ à partir de 1. Ensuite, vous pouvez ajouter une clause #else à la fin de votre liste #elsif pour détecter les cas où USER est accidentellement défini sur quelque chose que vous ne savez pas gérer. (Sinon, si vous numérotez à partir de 0, le cas 0 devient votre fourre-tout, car c'est la valeur numérique par défaut du préprocesseur pour les symboles non définis.)
sclamage

8

Utilisez des valeurs numériques au lieu de chaînes.

Enfin, pour convertir les constantes JACK ou QUEEN en chaîne, utilisez les opérateurs stringize (et / ou tokenize).


2

Comme déjà indiqué ci-dessus, le préprocesseur ISO-C11 ne prend pas en charge la comparaison de chaînes. Cependant, le problème de l'affectation d'une macro avec la «valeur opposée» peut être résolu avec le «collage de jetons» et «l'accès à la table». La macro-solution simple concaténer / stringify de Jesse échoue avec gcc 5.4.0 car la stringisation est effectuée avant l'évaluation de la concaténation (conformément à ISO C11). Cependant, il peut être corrigé:

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

La première ligne (macro P_()) ajoute une indirection pour laisser la ligne suivante (macro VS()) terminer la concaténation avant la stringisation (voir Pourquoi ai-je besoin d'une double couche d'indirection pour les macros? ). Les macros de stringisation ( S()et S_()) proviennent de Jesse.

La table (macros jack_VSet queen_VS) qui est beaucoup plus facile à maintenir que la construction if-then-else de l'OP est de Jesse.

Enfin, le bloc de quatre lignes suivant appelle les macros de style fonction. Le dernier bloc de quatre lignes provient de la réponse de Jesse.

Stocker le code foo.cet appeler le préprocesseur gcc -nostdinc -E foo.cdonne:

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



"jack"
"USER_VS"

La sortie est comme prévu. La dernière ligne montre que la USER_VSmacro n'est pas développée avant la stringisation.


Cela fonctionne bien, jusqu'à ce que j'essaye de comparer réellement la chaîne générée, pour faire une compilation conditionnelle: #if (S(USER)=="jack")- J'obtiens une erreur de préprocesseur lors de l'utilisation de "- error: invalid token at start of a preprocessor expression.
ysap

1

Si vos chaînes sont des constantes de temps de compilation (comme dans votre cas), vous pouvez utiliser l'astuce suivante:

#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

Le compilateur peut dire le résultat du strcmp à l'avance et remplacera le strcmp par son résultat, vous donnant ainsi une #define qui peut être comparée aux directives du préprocesseur. Je ne sais pas s'il y a une différence entre les compilateurs / dépendance sur les options du compilateur, mais cela a fonctionné pour moi sur GCC 4.7.2.

EDIT: après une enquête plus approfondie, il semble que ce soit une extension de chaîne d'outils, pas une extension GCC, alors prenez cela en considération ...


7
Ce n'est certainement pas du C standard, et je ne vois pas comment cela fonctionnerait avec n'importe quel compilateur. Le compilateur peut parfois indiquer les résultats des expressions (même les appels de fonction, s'ils sont en ligne), mais pas le pré-processeur. Utilisez-vous $une sorte d'extension de pré-processeur?
ugoren

3
On dirait que la syntaxe '#if $ USER_JACK == 0' fonctionne, du moins avec GNU C ++ utilisé pour créer du code Android natif (JNI) ... Je ne le savais pas, mais c'est très utile, merci de nous en parler il!
gregko

6
J'ai essayé ceci sur GCC 4.9.1, et je ne pense pas que cela fera ce que vous pensez que cela fera. Bien que le code se compile, il ne vous donnera pas le résultat attendu. «$» est traité comme un nom de variable. Ainsi, le préprocesseur recherche la variable '$ USER_JACK', ne la trouvant pas et lui donnant la valeur par défaut de 0. Ainsi, vous aurez toujours USER_VS défini comme USER_QUEEN quel que soit strcmp
Vitali

1

Les réponses de Patrick et de Jesse Chisholm m'ont fait faire ce qui suit:

#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

Au lieu de #define USER 'Q' #define USER QUEEN devrait également fonctionner mais n'a pas été testé fonctionne également et pourrait être plus facile à manipuler.

EDIT: D'après le commentaire de @ Jean-François Fabre j'ai adapté ma réponse.


changement (s==QUEEN?1:0)par (s==QUEEN)vous n'avez pas besoin du ternaire, le résultat est déjà un booléen
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

il s'agit essentiellement d'un tableau de caractères statiques de longueur fixe initialisé manuellement au lieu d'un tableau de caractères statiques de longueur variable initialisé automatiquement se terminant toujours par un caractère nul de fin


0

Vous ne pouvez pas faire cela si USER est défini comme une chaîne entre guillemets.

Mais vous pouvez le faire si USER est juste JACK, QUEEN ou Joker ou autre.

Il y a deux astuces à utiliser:

  1. Token-splicing, où vous combinez un identifiant avec un autre identifiant en concaténant simplement leurs caractères. Cela vous permet de comparer avec JACK sans avoir à #define JACKfaire quelque chose
  2. expansion de macro variadic, qui vous permet de gérer des macros avec un nombre variable d'arguments. Cela vous permet d'étendre des identifiants spécifiques en un nombre variable de virgules, qui deviendront votre comparaison de chaînes.

Alors commençons par:

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

Maintenant, si j'écris JACK_QUEEN_OTHER(USER)et que USER est JACK, le préprocesseur le transforme enEXPANSION1(ReSeRvEd_, JACK, 1, 2, 3)

La deuxième étape est la concaténation:

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

maintenant , JACK_QUEEN_OTHER(USER)devientEXPANSION2(ReSeRvEd_JACK, 1, 2, 3)

Cela donne la possibilité d'ajouter un certain nombre de virgules selon qu'une chaîne correspond ou non:

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

Si USER est JACK, JACK_QUEEN_OTHER(USER)devientEXPANSION2(x,x,x, 1, 2, 3)

Si USER est REINE, JACK_QUEEN_OTHER(USER)devientEXPANSION2(x,x, 1, 2, 3)

Si USER est autre, JACK_QUEEN_OTHER(USER)devientEXPANSION2(ReSeRvEd_other, 1, 2, 3)

À ce stade, quelque chose de critique s'est produit: le quatrième argument de la macro EXPANSION2 est 1, 2 ou 3, selon que l'argument d'origine passé était jack, queen ou autre. Tout ce que nous avons à faire est donc de le sélectionner. Pour de longues raisons, nous aurons besoin de deux macros pour la dernière étape; ce seront EXPANSION2 et EXPANSION3, même si l'une semble inutile.

En mettant tout cela ensemble, nous avons ces 6 macros:

#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

Et vous pourriez les utiliser comme ceci:

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
}

Lien godbolt obligatoire: https://godbolt.org/z/8WGa19


-5

C'est simple, je pense que tu peux juste dire

#define NAME JACK    
#if NAME == queen 
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.