Почему препроцессор 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
но делает код менее читаемым.
Есть ли элегантное решение для этого?
Ответы
Ответ 1
Нет макросов, называемых A
или B
, поэтому на вашей строке #if
, A
и B
заменить на 0
, так что вы действительно имеете:
enum T { A = 1, B = 2 };
int main() {
#if (0 == 0)
std::cout << A << B;
#endif
}
Препроцессор запускается до того, как компилятор ничего знает о вашем enum
. Препроцессор знает только о макросах (#define
).
Ответ 2
Это связано с тем, что препроцессор работает до времени компиляции.
Поскольку определения перечисления происходят во время компиляции, 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 который правильно ответил ниже.
Ответ 3
Идентификаторы, которые не определены макросами, интерпретируются как значение 0 в условных инструкциях препроцессора. Поэтому, поскольку вы не определили макросы A
и B
, они оба считаются 0, а два 0 равны друг другу.
Причина, по которой идентификаторы undefined (для предварительного процессора) считаются 0, заключается в том, что он позволяет использовать макросы undefined в условном выражении без использования #ifdef
.
Ответ 4
Препроцессор запускается перед компилятором, что означает, что препроцессор ничего не знает о символах, определенных компилятором, и поэтому он не может действовать в зависимости от них.
Ответ 5
Как и другие ответы, препроцессор C не видит перечислений. Он ожидает и может только понять макросы.
Per стандарт C99, §6.10.1 (Условное включение):
После того, как все замены из-за расширения макроса и определенного унарного оператора выполнены, все остальные идентификаторы заменяются на pp-число 0
Другими словами, в директиве #if или #elif любые макросы, которые нельзя развернуть, потому что они не существуют/являются undefined, будут вести себя точно так, как если бы они были определены как 0, и поэтому всегда будут равны друг другу.
Вы можете поймать вероятное непреднамеренное поведение, подобное этому в GCC/clang, с опцией предупреждения - Wundef (вы, вероятно, захотите сделать его фатальным с -Werror = undef).
Ответ 6
Другие ответы объясняют, почему то, что вы пытаетесь, не работает; для альтернативы, я бы, вероятно, пошел с:
#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, если существует опасность столкновений с "реальным" исходным кодом.
Ответ 7
Посты объяснили, почему, но возможное решение для вас, которое поддерживает читаемость, может быть таким:
#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