Может ли #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 несовместимыми в отказе от моего кода?
Ответы
Ответ 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
необходимо сопоставить в одном файле.