Подтвердить что ты не робот

Может ли #endif во включенном файле использоваться для закрытия #if в включенном файле?

Скажем, у меня есть два файла, a.h:

#if 1
#include "b.h"

и b.h:

#endif

Оба препроцессора gcc и clang отклоняют a.h:

$ cpp -ansi -pedantic a.h >/dev/null
In file included from a.h:2:0:
b.h:1:2: error: #endif without #if
 #endif
  ^
a.h:1:0: error: unterminated #if
 #if 1
 ^

Однако стандарт C (N1570 6.10.2.3) говорит:

Директива предварительной обработки формы

# include "q-char-sequence" new-line

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

который, как представляется, позволяет построить выше.

Являются ли gcc и clang несовместимыми в отказе от моего кода?

4b9b3361

Ответ 1

Стандарт C определяет 8 фаз перевода. Исходный файл обрабатывается каждой из 8 фаз в последовательности (или эквивалентным образом).

Этап 4, как определено в N1570 в разделе 5.1.1.2, есть:

Выполняются предпроцессорные директивы, расширяются макро-вызовы, и _Pragma выполняются унарные операторские выражения. Если последовательность символов, которая соответствует синтаксису универсального символа имя создается путем конкатенации токенов (6.10.3.3), поведение undefined. A #include директива предварительной обработки вызывает заголовок или исходный файл, подлежащий обработке с этапа 1 по фазе 4, рекурсивно. Все директивы предварительной обработки затем удаляются.

Соответствующее предложение здесь:

A #include директива предварительной обработки вызывает заголовок или исходный файл, подлежащий обработке с этапа 1 по фазе 4, рекурсивно.

который подразумевает, что каждый включенный исходный файл предварительно обрабатывается сам по себе. Это исключает возможность наличия #if в одном файле и соответствующего #endif в другом.

(Как говорится в "Диком слоне" в комментариях, а rodrigo answer говорит, что в грамматике в разделе 6.10 также говорится, что if-section, который начинается с a #if (или #ifdef или #ifndef) и заканчивается линией #endif, может отображаться только как часть файла предварительной обработки.)

Ответ 2

Я думаю, что компиляторы правы, или, в лучшем случае, стандарт неоднозначен.

Трюк не в том, как реализуется #include, а в том порядке, в котором выполняется предварительная обработка.

Посмотрите правила грамматики в разделе 6.10 стандарта C99:

preprocessing-file:
    group[opt]

group:
    group-part
    group group-part

group-part:
    if-section
    control-line
    text-line
    # non-directive

if-section:
    if-group elif-groups[opt] else-group[opt] endif-line

if-group:
    # if constant-expression new-line group[opt]
...
control-line:
    # include pp-tokens new-line
    ...

Как вы можете видеть, материал #include вложен внутри group, а group - это вещь внутри #if / #endif.

Например, в хорошо сформированном файле, например:

#if 1
#include <a.h>
#endif

Это будет анализироваться как #if 1, плюс group, плюс #endif. А внутри group есть #include.

Но в вашем примере:

#if 1
#include <a.h>

Правило if-section не применяется к этому источнику, поэтому производственные операции group даже не проверяются.

Возможно, вы можете утверждать, что стандарт неоднозначен, поскольку он не указывает, когда происходит замена директивы #include, и что соответствующая реализация может сменить множество правил грамматики и заменить #include до сбоя не найдя #endif. Но эти двусмысленности невозможно избежать, если побочные эффекты синтаксиса изменяют текст, который вы разыгрываете. Разве это не замечательно?

Ответ 3

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

  • Лексический анализ. Группирует последовательность символов, составляющих блок перевода предварительной обработки, в строки, имеющие обозначенное значение (токены) на языке препроцессора.
  • Синтаксический анализ. Группирует маркеры блока обработки предварительной обработки в синтаксические структуры, построенные в соответствии с грамматикой языка предварительной обработки.
  • Генерация кода. Переводит все файлы, составляющие блок перевода предварительной обработки, в один файл, содержащий только "чистые" инструкции C.
Строго говоря, фазы перевода, упомянутые в п. 5.1.1.2 стандарта C (ISO/IEC 9899: 201x), касающиеся предварительной обработки, фаза 3 и фаза 4. Фаза 3 практически соответствует лексическому анализу, а фаза 4 - генерации кода.

Синтаксический анализ (синтаксический анализ), кажется, отсутствует на этой картинке. Действительно, грамматика препроцессора C настолько проста, что реальные препроцессоры/компиляторы выполняют ее вместе с лексическим анализом.

Если этап синтаксического анализа завершается успешно - то есть все заявления в блоке перевода препроцессора являются законными в соответствии с грамматикой препроцессора, - может происходить генерация кода и все директивы предварительной обработки.
Выполнение директивы предварительной обработки означает преобразование исходного файла в соответствии с его семантикой, а затем удаление директивы из исходного файла.
Семантика для каждой директивы препроцессора указана в §6.10.1-6.10.9 Стандарта C.

Возвращаясь к вашей примерной программе, 2 файла, которые вы предоставили, т.е. a.h и b.h, концептуально обрабатываются следующим образом.

Лексический анализ. Каждый отдельный токен предварительной обработки разделяется символом "{" слева и "}" справа.

a.h

{#}{if} {1}
{#}{include} {"b.h"}

b.h

{#}{endif}

Эта фаза выполняется без ошибок, и ее результат, последовательность токенов предварительной обработки, передается в следующую фазу: синтаксический анализ.

Синтаксический анализ

Примерный вывод для a.h приведен ниже

preprocessing-file →
group →
group-part →
if-section →
if-group endif-line → 
if-group #endif new-line →
…

и ясно, что содержимое a.h не может быть выведено из грамматики предварительной обработки - фактически отсутствует завершающий #endif - и, следовательно, a.h не является синтаксически правильным. Это именно то, о чем говорит ваш компилятор при записи

a.h:1:0: error: unterminated #if

Что-то подобное происходит при b.h; рассуждая назад, #endif может быть получен только из правила

if-section → 
if-group elif-groups[opt] else-group[opt] endif-line

Это означает, что содержимое файла должно быть получено из одной из следующих трех групп

# if constant-expression new-line group[opt]
# ifdef identifier new-line group[opt]
# ifndef identifier new-line group[opt]

Так как это не так, потому что b.h не содержит # if/# ifdef/# ifndef, а только единственную строку #endif, содержимое b.h не является синтаксически правильным, и ваш компилятор сообщает вам об этом таким образом

In file included from a.h:2:0:
b.h:1:2: error: #endif without #if

Генерация кода

Конечно, поскольку ваша программа лексически звучит, но синтаксически неверна, эта фаза никогда не выполняется.

Ответ 4

#if / #ifdef / #ifndef
#elif
#else
#endif

необходимо сопоставить в одном файле.