Логическое И (&&) неправильно закорачивается в #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.

Это проблема C23 __has_include и подобных макросов. И был поднят рецензентом на веб-сайте CodeReview.

Harith 30.06.2024 15:44

Один из способов подумать об этом состоит в том, что даже если второй операнд не может быть вычислен, его все равно необходимо успешно проанализировать, а правила синтаксиса в препроцессоре отличаются от правил в самом C. Поведение «короткого замыкания» в C на самом деле связано с побочными эффектами, которые могут иметь выражения второго операнда, и это не имеет отношения к препроцессору, поскольку там выражения никогда не имеют побочных эффектов.

Nate Eldredge 30.06.2024 18:54

@Харит – Когда вы писали, когда FOO не определено, вы имели в виду «когда FOO не определено», верно?

Armali 09.07.2024 11:32

@Армали Да. Зафиксированный.

Harith 09.07.2024 13:00
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
12
4
1 683
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 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

Интересный. Как тогда мне написать #if defined(__has_include) && __has_include(<stdbit.h>) так, чтобы не вызвать сбой компиляции, если макрос __has_include не был определен?

Harith 30.06.2024 15:53

@Харит: Используйте вложенные директивы #if.

Eric Postpischil 30.06.2024 15:54

@Харит: В проекте стандарта 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).

Jonathan Leffler 01.07.2024 01:50

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

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

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

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

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

Другие вопросы по теме