Для кода:
#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. Поведение «короткого замыкания» в C на самом деле связано с побочными эффектами, которые могут иметь выражения второго операнда, и это не имеет отношения к препроцессору, поскольку там выражения никогда не имеют побочных эффектов.
@Харит – Когда вы писали, когда FOO не определено, вы имели в виду «когда FOO не определено», верно?
@Армали Да. Зафиксированный.





Если 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
Интересный. Как тогда мне написать #if defined(__has_include) && __has_include(<stdbit.h>) так, чтобы не вызвать сбой компиляции, если макрос __has_include не был определен?
@Харит: Используйте вложенные директивы #if.
@Харит: В проекте стандарта C23 есть «ПРИМЕР 2», который иллюстрирует: /* Fallback for compilers not yet implementing this feature. */ — #ifndef __has_c_attribute — #define __has_c_attribute(x) 0 — #endif /* __has_c_attribute */ — #if __has_c_attribute(fallthrough) — … — #endif. Аналогичную операцию можно использовать для __has_include (и __has_embed).
Директивы препроцессора решаются на этапе препроцессора. Это происходит до начала фактической компиляции C. А это означает, что выражение должно интерпретироваться и оцениваться при чтении файла, а не при выполнении программы.
Это означает, что препроцессор пытается проверить выражение, указанное в директиве, интерпретируя его, а не выполняя. Как вам сказали в другом ответе, если макрос не определен, то FOO(что-то) является синтаксической ошибкой препроцессора. Допустим, вместо этого вы сказали:
#if defined(A) && FOO(something)
Поскольку два выражения вокруг оператора && должны быть интерпретированы, в случае, если FOO() был макросом, он будет расширен, а затем интерпретирован перед вычислением выражения. Я не могу сделать вывод о последствиях вычисления правой части логического выражения на основе результатов левой части, потому что во время препроцессора все статично (никакие изменения не происходят в результате вычисления/невычисления выражения препроцессора). В препроцессоре нет побочных эффектов, поэтому для составления дерева выражений придется интерпретировать обе части... то, как препроцессор вычисляет выражение, зависит от разработчика препроцессора.
Замыкание влияет на выполнение (то есть во время выполнения) и видно только в том случае, если одна из сторон оператора && имеет побочные эффекты. Это не дело препроцессора.
Это проблема C23
__has_includeи подобных макросов. И был поднят рецензентом на веб-сайте CodeReview.