Логическое И (&&) неправильно закорачивается в #if

Для кода:

#if defined(FOO) && FOO(foo)
    #error "FOO is defined."
#else
    #error "FOO is not defined."
#endif

MSVC 19.38 печатает:

<source>(1): warning C4067: unexpected tokens following preprocessor directive - expected a newline
<source>(4): fatal error C1189: #error:  "FOO is not defined."

ICX 2024.0.0 и Clang 18.1 печатают:

<source>:1:21: error: function-like macro 'FOO' is not defined
    1 | #if defined(FOO) && FOO(foo)
      |                     ^
<source>:4:6: error: "FOO is not defined."
    4 |     #error "FOO is not defined."
      |      ^
2 errors generated.

GCC 14.1 печатает:

<source>:1:24: error: missing binary operator before token "("
    1 | #if defined(FOO) && FOO(foo)
      |                        ^
<source>:4:6: error: #error "FOO is not defined."
    4 |     #error "FOO is not defined."
      |      ^~~~~
Compiler returned: 1

Почему каждый компилятор, кроме MSVC, выводит ошибку о неопределенном макросе, когда FOO не определен (хотя MSVC тоже выводит предупреждение)? Есть ли какая-то особая семантика, которую я здесь не вижу?

FOO(foo) не следует оценивать, если defined(FOO) оценивается как false.

🤔 А знаете ли вы, что...
C используется для создания библиотек и фреймворков, которые расширяют его функциональность.


12
1 683
2

Ответы:

Решено

Если FOO не определен (или определен, но не как макрос, подобный функции), то FOO(foo) является синтаксической ошибкой.

Директива #if ожидает, что за ней будет следовать целочисленное константное выражение (включая выражения формы «определенный идентификатор»). Поскольку FOO(foo) не может быть расширено из-за того, что FOO не определено, это не целочисленное константное выражение.

Вы получите аналогичную ошибку для чего-то вроде этого:

int main()
{
    int x = some_valid_expression && undeclared_identifier;
    return 0;
}

Чтобы сделать то, что вы хотите, вам нужно разбить директиву #if на несколько:

#if defined(FOO)
    #if FOO(foo)
        #error "FOO is defined and non-zero."
    #else
        #error "FOO is zero."
    #endif
#else
    #error "FOO is not defined."
#endif

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

Это означает, что препроцессор пытается проверить выражение, указанное в директиве, интерпретируя его, а не выполняя. Как вам сказали в другом ответе, если макрос не определен, то FOO(что-то) является синтаксической ошибкой препроцессора. Допустим, вместо этого вы сказали:

#if defined(A) && FOO(something)

Поскольку два выражения вокруг оператора && должны быть интерпретированы, в случае, если FOO() был макросом, он будет расширен, а затем интерпретирован перед вычислением выражения. Я не могу сделать вывод о последствиях вычисления правой части логического выражения на основе результатов левой части, потому что во время препроцессора все статично (никакие изменения не происходят в результате вычисления/невычисления выражения препроцессора). В препроцессоре нет побочных эффектов, поэтому для составления дерева выражений придется интерпретировать обе части... то, как препроцессор вычисляет выражение, зависит от разработчика препроцессора.

Замыкание влияет на выполнение (то есть во время выполнения) и видно только в том случае, если одна из сторон оператора && имеет побочные эффекты. Это не дело препроцессора.