2015-09-29 3 views
12

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

#if 1 
#include "b.h" 

и b.h:

#endif 

Оба ССЗ и препроцессор 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 (N15706.10.2.3) говорит:

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

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

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

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

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

+2

Даже если положить '# if' в один файл и соответствующий' # endif' в другой законный, это будет ИМХО быть плохой идеей. –

ответ

12

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

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

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

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

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

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

(как «дикий слон» упоминается в комментариях, и, как rodrigo's answer говорит, грамматика в разделе 6.10 также говорит о том, что раздел, если-, который начинается с #if (или #ifdef или #ifndef) линии и заканчивается #endif линия, может появиться только как часть предобработки-файл.)

+0

Замечательно, спасибо! Если вам интересно, то, что заставило меня спросить об этом, - это абзац из руководства «Сделать»: https://twitter.com/whitequark/status/648870461514850305 – whitequark

+1

Иными словами, «каждый включенный исходный файл обрабатывается через фазу перевода 4 до включающий исходный файл может завершить фазу 4' – Les

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

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

+5

Поскольку язык C определен стандартом C, можете ли вы указать, где это требует стандарт C? Я не мог найти такого требования, заявленного где угодно. – whitequark

+0

@whitequark. Правило if-section синтаксиса, описанного в 6.10, требует этого. –

+0

@ Уилделлефант вы можете уточнить? В разделе 6.10.1.6 говорится только, что соответствующая группа (группы) пропускается; он не упоминает файлы. Например. в 'все группы, пока #endif не будут пропущены'. – whitequark

0

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

  1. Лексический анализ - Группы последовательность символов, составляющих блок предварительной обработки перевода в строках, имеющих идентифицированного значение (маркеры) на языке препроцессора.
  2. Синтаксический анализ - Группирует маркеры блока предварительной обработки в синтаксические структуры, построенные в соответствии с грамматикой языка предварительной обработки.
  3. Генерация кода - Переводит все файлы, составляющие блок перевода предварительной обработки, в один файл, содержащий только «чистые» инструкции C.

Строго говоря, фазы перевода, упомянутые в §5.1.1.2 части C Standard (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} 

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

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

Предварительный вывод для ах приведен ниже

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

, и ясно, что содержание ах не может быть выведена из грамматики предварительной обработки - на самом деле согласующим #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 

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

# 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 

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

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

7

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

Трюк не в том, как реализован #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. Но эти двусмысленности невозможно избежать, если побочные эффекты синтаксиса изменяют текст, который вы разыгрываете. Разве это не замечательно?

+0

А, это хороший момент. Хотел бы я принять два ответа! – whitequark

Смежные вопросы