web-dev-qa-db-fra.com

Pourquoi le préprocesseur C considère-t-il que les valeurs enum sont égales?

Pourquoi le std::cout la ligne du code suivant s'exécute même si A et B sont différents?

#include <iostream>

enum T { A = 1, B = 2 };
// #define A 1
// #define B 2

int main() {
#if (A == B)
    std::cout << A << B;
#endif
}

Si j'utilise #define à la place (comme commenté), je n'obtiens aucune sortie comme je m'y attendais.

Motif de la question:

Je veux avoir un sélecteur de mode pour un code de test dans lequel je peux facilement changer de mode en commentant/décommentant les lignes en haut:

enum T { MODE_RGB = 1, MODE_GREY = 2, MODE_CMYK = 3 };
// #define MODE MODE_RGB
#define MODE MODE_GREY
// #define MODE MODE_CMYK

int main() {
#if (MODE == MODE_RGB)
    // do RGB stuff
#Elif (MODE == MODE_GREY)
    // do greyscale stuff
#else
    // do CMYK stuff
#endif

    // some common code

    some_function(arg1, arg2,
#if (MODE == MODE_RGB)
        // RGB calculation for arg3,
#Elif (MODE == MODE_GREY)
        // greyscale calculation for arg3,
#else
        // CMYK calculation for arg3,
#endif
        arg4, arg5);
}

Je sais que je peux utiliser des valeurs numériques, par exemple.

#define MODE 1 // RGB
...
#if (MODE == 1) // RGB

mais cela rend le code moins lisible.

Existe-t-il une solution élégante pour cela?

61
Gnubie

Il n'y a pas de macros appelées A ou B, donc votre #if line, A et B sont remplacés par 0, vous avez donc:

enum T { A = 1, B = 2 };

int main() {
#if (0 == 0)
    std::cout << A << B;
#endif
}

Le préprocesseur s'exécute avant que le compilateur ne sache quoi que ce soit sur votre enum. Le préprocesseur ne connaît que les macros (#define).

120
Simple

En effet, le préprocesseur fonctionne avant l'heure de compilation.

Comme les définitions d'énumération se produisent au moment de la compilation, A et B seront tous deux définis comme vides (pp-number 0) - et donc égal - au moment du prétraitement, et donc l'instruction de sortie est incluse dans le code compilé.

Lorsque vous utilisez #define elles sont définies différemment au moment du prétraitement et donc l'instruction est fausse.

En ce qui concerne votre commentaire sur ce que vous voulez faire, vous n'avez pas besoin d'utiliser un pré-processeur #if pour faire ça. Vous pouvez simplement utiliser la norme if comme MODE et MODE_GREY (ou MODE_RGB ou MODE_CMYK) sont toujours définis:

#include <iostream>

enum T { MODE_RGB = 1, MODE_GREY = 2, MODE_CMYK = 3 };

#define MODE MODE_GREY

int main()
{
    if( MODE == MODE_GREY )
        std::cout << "Grey mode" << std::endl;
    else if( MODE == MODE_RGB )
        std::cout << "RGB mode" << std::endl;
    else if( MODE == MODE_CMYK )
        std::cout << "CMYK mode" << std::endl;

    return 0;
}

L'autre option utilisant uniquement le préprocesseur est de le faire comme @TripeHound correctement répondu ci-dessous .

44
Samidamaru

Les identificateurs qui ne sont pas des macros définies sont interprétés comme la valeur 0 dans les directives de préprocesseur conditionnel. Par conséquent, puisque vous n'avez pas défini de macros A et B, elles sont toutes deux considérées comme 0 et deux 0 sont égales l'une à l'autre.

La raison pour laquelle les identifiants non définis (pour le pré-processeur) sont considérés comme 0 est parce qu'elle permet d'utiliser des macros non définies au conditionnel sans utiliser #ifdef.

24
eerorika

Le préprocesseur s'exécute avant le compilateur, ce qui signifie que le préprocesseur ne sait rien des symboles définis par le compilateur et qu'il ne peut donc pas agir en fonction d'eux.

11
Ulrich Eckhardt

Comme les autres réponses l'ont dit, le préprocesseur C ne voit pas d'énumérations. Il attend et ne peut comprendre que les macros.

Par la norme C99 , §6.10.1 (inclusion conditionnelle):

Une fois tous les remplacements dus à l'expansion des macros et à l'opérateur unaire défini effectués, tous les identifiants restants sont remplacés par le numéro pp 0

En d'autres termes, dans une directive #if ou #Elif, toutes les macros qui ne peuvent pas être développées, car elles n'existent pas/ne sont pas définies, se comporteront exactement comme si elles avaient été définies comme 0, et seront donc toujours égales à L'un et l'autre.

Vous pouvez attraper un comportement imprévu comme celui-ci dans GCC/clang avec l'option d'avertissement - Wundef (vous voudrez probablement le rendre fatal avec -Werror = undef).

10
user10063

D'autres réponses expliquent pourquoi ce que vous essayez ne fonctionne pas; pour une alternative, j'irais probablement avec:

#define RGB 1
#define GREY 2
#define CMYK 3
#define MODE RGB

#if MODE == RGB
    //RGB-mode code
#Elif MODE == GREY
    //Greyscale code
#Elif MODE == CMYK
    //CMYK code
#else
#    error Undefined MODE
#endif

Vous voudrez peut-être des préfixes sur le RVB/GRIS/CMJN s'il y a un risque de conflits avec le "vrai" code source.

7
TripeHound

Les messages ont expliqué pourquoi, mais une solution possible pour vous qui conserve la lisibilité pourrait être comme ceci

#define MODE_RGB

int main()
{        
    #ifdef MODE_RGB
        std::cout << "RGB mode" << std::endl;
    #Elif defined MODE_GREY
        std::cout << "Grey mode" << std::endl;
    #Elif defined MODE_CMYK
        std::cout << "CMYK mode" << std::endl;
    #endif
}

Il vous suffit alors de changer la macro en haut, seule la macro qui vous intéresse est définie. Vous pouvez également inclure une vérification pour vous assurer qu'un et un seul est défini et sinon, et faites #error "You must define MODE_RGB, MODE_GREY or MODE_CMYK

2
noggin182