Кросс-файл #if и #endif - должно ли это быть законным?

Согласно стандарту C11,

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

# включить новую строку "q-char-sequence"

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

Итак, если у меня есть заголовочный файл test.h, содержащий:

#endif

И исходный файл test.c, содержащий:

#if 1
#include "test.h"

Разве он не должен пройти фазу предварительной обработки по стандарту, заменив содержимое test.h на место?

Но я не могу сделать это с clang, который говорит:

In file included from test.c:2:
./test.h:1:2: error: #endif without #if
#endif
 ^
test.c:1:2: error: unterminated conditional directive
#if 1
 ^
2 errors generated.

Итак, каково поведение, указанное в стандарте?


person Hai Zhang    schedule 14.10.2016    source источник


Ответы (2)


Если вы читаете, например. данный #include справочник по C++ включаемый файл сначала проходит через этапы перевода с первого по четвертый и четвертая фаза запускает препроцессор (рекурсивно).

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

Это происходит и в C.


Прочитав спецификацию C11 (ISO/IEC 9899:2011 [2012]), думаю, происходит следующее:

Компилятор находится на этапе препроцессора (этап 4) и оценивает #if 1 директива препроцессора. Условие оценивается как истинное, поэтому оно входит в блок внутри условия. Видит директиву #include "test.h".

Когда компилятор обрабатывает директиву include, он временно останавливает обработку текущего файла, чтобы обработать включенный файл. Эта обработка включенного файла проходит этапы компиляции с 1 по 4 (включительно), прежде чем продолжить работу с текущим исходным файлом.

Когда обработка самого включенного заголовочного файла доходит до фазы 4 и начинает обрабатывать директиву #endif, то он не поднимается вверх в стеке рекурсивного включения, чтобы найти соответствующий #if, препроцессор только ищет в текущем кадре стека (текущем файле). Поэтому вы получаете первую ошибку об отсутствии #if. На самом деле стандарт ничего не говорит об этом. В основном это говорит о том, что #if должно соответствовать #endif. То, что компилятор не поднимается по стеку включения, чтобы найти соответствие #if, похоже, больше относится к деталям реализации.

В любом случае, препроцессор заканчивает обработку заголовочного файла на шаге 3 в фазе 4, т.е.

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

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

person Some programmer dude    schedule 14.10.2016
comment
Этот ответ ничего не объясняет. Вся предварительная обработка происходит в одной и той же фазе, поэтому это не помогает понять, что происходит. Пожалуйста, смотрите мой ответ ниже. - person Jens Gustedt; 14.10.2016

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

Это раздел 6.10.1 p6 стандарта C.

person Jens Gustedt    schedule 14.10.2016
comment
Хотя мой ответ не совсем правильный, это тоже. Если компилятор будет сканировать исходный файл, чтобы найти совпадающие #elif/#else/#endif, то он вообще не будет включать заголовочный файл "test.h", и единственная ошибка будет связана с незавершенным #if. Стандарт (я читал C11 (издание 2012 г.)) ничего не говорит о том, как компилятор должен сопоставлять директиву препроцессора #if с #endif. Сканирование, о котором вы упоминаете, является деталью реализации, не предусмотренной стандартом. - person Some programmer dude; 14.10.2016