Почему препроцессор C считает значения перечисления равными?


почему std::cout строка в следующем коде выполняется, даже если A и B разные?

#include <iostream>

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

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

если я использую #define вместо этого (как прокомментировано), я не получаю никакого результата, как я ожидаю.

причина вопроса:

Я хочу, чтобы переключатель режимов на некоторые тестовый код, в котором я могу легко изменить режимы, комментируя/раскомментировав строки сверху:

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

Я знаю, что могу использовать числовые значения, например

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

но это делает код менее читаемым.

есть элегантное решение для этого?

7 58

7 ответов:

макросы не называются A или B С #if линия, A и B заменить на 0, так что у вас на самом деле есть:

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

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

препроцессор запускается до того, как компилятор узнает что-либо о вашем enum. Препроцессор знает только о макросах (#define).

это потому, что препроцессор работает до компиляции.

поскольку определения перечисления происходят во время компиляции, A и B будут определены как пустые (pp-number 0) - и, таким образом, равно-во время предварительной обработки, и, таким образом, оператор вывода включается в скомпилированный код.

при использовании #define они определяются по-разному во время предварительной обработки, и поэтому оператор оценивается как false.

в отношении вашего комментария о том, что вы хотите сделать, вам не нужно использовать препроцессор #if для этого. Вы можете просто использовать стандартный if как MODE и MODE_GREY (или MODE_RGB или MODE_CMYK) все еще определено:

#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;
}

другой вариант, используя только предварительный процессор, должен сделать это как @TripeHound правильно ответили ниже.

идентификаторы, которые не определены макросы интерпретируются как значение 0 в директивах условного препроцессора. Поэтому, поскольку вы не определили макрос A и B оба они являются 0 и 0 равны друг другу.

причина, по которой неопределенные (для препроцессора) идентификаторы считаются 0, заключается в том, что он позволяет использовать неопределенные макросы в условном без использования #ifdef.

препроцессор запускается перед компилятором, который означает, что препроцессор ничего не знает о символах определяются компилятором и поэтому он не может действовать в зависимости от них.

как и другие ответы, препроцессор C не видит перечислений. Он ожидает и может только понять макросы.

на стандарт C99, §6.10.1 (условное включение):

после выполнения всех замен, вызванных расширением макроса и определенным унарным оператором, все остальные идентификаторы заменяются на pp-число 0

другими словами, в директиве #if или #elif любые макросы, которые не могут быть расширенные, поскольку они не существуют / не определены, будут вести себя точно так же, как если бы они были определены как 0, и поэтому всегда будут равны друг другу.

вы можете поймать вероятное непреднамеренное поведение, подобное этому, в GCC/clang с опцией предупреждения -Wundef (вы, вероятно, захотите сделать его фатальным с-Werror=undef).

другие ответы объясняют, почему то, что вы пытаетесь не работает; для альтернативы, я бы, вероятно, пошел с:

#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

вам могут понадобиться префиксы на RGB/GREY / CMYK, если есть опасность столкновения с "реальным" исходным кодом.

сообщения объяснили, почему, но возможное решение для вас, которое сохраняет читаемость, может быть таким

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

вам просто нужно изменить макрос вверху, чтобы только макрос, который вас интересует, был определен. Вы также можете включить проверку, чтобы убедиться, что один и только один определен, а если нет, то и сделать #error "You must define MODE_RGB, MODE_GREY or MODE_CMYK