• R.16.1 Фазы препроцессорной обработки
  • R.16.2 Триграфные последовательности
  • R.16.3 Макроопределение и макроподстановка
  • R.16.3.1 Операция #
  • R.16.3.2 Операция ##
  • R.16.3.3 Повторный просмотр и дальнейшие подстановки
  • R.16.3.4 Область видимости макроимен и конструкция #undef
  • R.16.4 Включение файлов
  • R.16.5 Условная трансляция
  • R.16.6 Управление строками
  • R.16.7 Команда error
  • R.16.8 Команда pragma
  • R.16.9 Пустая директива
  • R.16.10 Предопределенные макроимена
  • R.16 Препроцессорная обработка

    Реализация языка C++ включает препроцессор с возможностями макроподстановки, условной трансляции и включения указанных файлов.

    Для передачи заданий препроцессору служат строки, начинающиеся с символа # (перед ним могут идти пробелы и символы горизонтальной табуляции). Такие строки называются командами, и их синтаксис определяется независимо от остального языка. Команды могут находиться в любом месте программы, и их действие продолжается (независимо от правил областей видимости С++) до конца данной единицы трансляции (§R.2).

    Команду препроцессора, как и любую строку, можно продолжить на следующей строке входного текста, поместив символ обратной дробной черты непосредственно перед символом конца продолжаемой строки. Препроцессор до того, как входная строка будет разбита на лексемы, удаляет символы обратной дробной черты и конца строки. Символ обратной дробной черты не должен быть последним символом входного файла.

    К лексемам препроцессора относятся: лексемы самого языка (§R.2.1), имя файла, которое используется в команде #include и вообще любой символ, отличный от обобщенного пробела и несовпадающий ни с какой из лексем препроцессора.

    R.16.1 Фазы препроцессорной обработки

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

    Перечислим их.

    При необходимости символы, зависящие от системы символы, обозначающие конец строки, заменяются на стандартный символ конца строки. Аналогичной замене подлежат все зависящие от системы символы. Определенные последовательности символов (триграфы) заменяются на эквивалентный им отдельный символ (§R.16.2).

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

    Входной текст разбивается на лексемы препроцессора и последовательность обобщенных пробелов. Каждый комментарий заменяется на один пробел. Входной текст не должен кончаться посреди лексемы или комментария.

    Выполняются команды препроцессора, и производятся макроподстановки (§R.16.3, §R.16.4, §R.16.5, §R.16.6, §R.16.7 и §R.16.8).

    В символьных константах и строках литералов комбинации специальных символов заменяются на свои эквиваленты (§R.2.5.2).

    Сливаются соседние строки литералов.

    Результат препроцессорной обработки подвергается синтаксическому и семантическому анализу, транслируется, а затем связывается с необходимыми библиотеками и другими программами.

    R.16.2 Триграфные последовательности

    Прежде чем начнется какая-либо иная препроцессорная обработка, каждое вхождение триграфной последовательности заменяется на один символ в соответствии с приведенной ниже таблицей.

    ??= #
    ??/ \
    ??' ^
    ??( [
    ??) [
    ??! |

    Например, строка

    ??=define arraycheck(a,b) a??(b??) ??!??! b??(a??)

    преобразуется в

    #define arraycheck(a,b) a[b] || b[a]

    R.16.3 Макроопределение и макроподстановка

    Команда вида

    #define идентификатор строка-лексем

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

    #define SIDE 8

    описание

    char chessboard[side][side];

    после макроподстановки примет вид

    char chessboard[8][8];

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

    Команда вида

    идентификатор ( идентификатор , … , идентификатор ) строка-лексем

    называется макроопределением с параметрами или "функциональным" макроопределением. В нем недопустимы пробелы между первым идентификатором и символом (. Определенный таким способом идентификатор можно переопределить с помощью другого функционального макроопределения, но при условии, что во втором определении то же число и те же наименования параметров, что и в первом, а обе строки замены совпадают. Все символы обобщенного пробела, разделяющие лексемы, считаются идентичными.

    Последующие вхождения идентификатора, определенного в функциональном макроопределении, если за ним следуют символ (, последовательность лексем, разделенных запятыми, и символ ), заменяются на строку лексем из макроопределения. Обобщенные пробелы, окружающие строку замены, отбрасываются. Каждое вхождение идентификатора, из списка параметров макроопределения, заменяется на последовательность лексем, представляющую соответствующий фактический параметр в макровызове. Фактическими параметрами являются строки лексем, разделенные запятыми. Запятая, взятая в кавычки, или находящаяся в символьной константе или во вложенных круглых скобках, не разделяет параметров. Число фактических параметров макровызова должно совпадать с числом параметров макроопределения.

    После идентификации параметров для функционального макроопределения происходит подстановка фактических параметров. После выполнения подстановок в параметре (если они были) этот параметр в строке замены замещается фактическим параметром из макровызова (§R.16.3.3); исключения составляют случаи, когда параметру предшествует лексема # (§R.16.3.1), или с ним соседствует лексема ## (§R.16.3.2).

    Приведем пример. Пусть есть макроопределения

    #define index_mask 0XFF00

    #define extract(word,mask) word & mask

    Тогда макровызов

    index = extract(packed_data,index_mask);

    после подстановки примет вид

    index = packed_data & 0XFF00;

    Для обоих видов макроопределений строка замены проверяется на наличие других макроопределений (§R.16.3.3).

    R.16.3.1 Операция #

    Если непосредственно перед параметром в строке замены идет лексема #, то при подстановке параметр и операция # будут заменены на строку литералов, содержащую имя соответствующего параметра макровызова. В символьной константе или строке литералов, входящих в параметр, перед каждым вхождением \ или " вставляется символ \.

    Например, если есть макроопределения

    #define path(logid,cmd) "/usr/" #logid "/bin/" #cmd

    то макровызов

    char* mytool=path(joe,readmail);

    приведет к такому результату:

    char* mytool="/usr/" "joe" "/bin/" "readmail";

    После конкатенации соседних строк (§R.16.1) получим:

    char* mytool="/usr/joe/bin/readmail";

    R.16.3.2 Операция ##

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

    Пусть есть макроопределение,

    #define inherit(basenum) public Pubbase ## basenum, \

     private Privbase ## basenum

    тогда макровызов

    class D: inherit(1) {};

    приведет к такому результату:

    class D: public Pubbase1, Privbase1 {};

    Макроопределение, которое в строке замены соседствует с ##, не подлежит подстановке, однако, результат конкатенации может использоваться для подстановки. Приведем пример. Пусть есть определения:

    #define concat(a) a ## ball

    #define base B

    #define baseball sport

    Тогда макровызов

    concat(base)

    даст в результате

    sport

    а вовсе не

    Bball

    R.16.3.3 Повторный просмотр и дальнейшие подстановки

    После того, как в строке замены произошла подстановка всех параметров макровызова, получившаяся строка просматривается повторно для обнаружения дополнительных макроопределений. Если в процессе повторных просмотров строки замены найдено имя макроопределения, то подстановка все же не происходит.

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

    R.16.3.4 Область видимости макроимен и конструкция #undef

    После появления макроопределения идентификатор из него считается определенным и остается в текущей области видимости (независимо от правил областей видимости в С++) до конца единицы трансляции или пока его определение не будет отменено с помощью команды #undef.

    Команда #undef имеет вид:

    #undef идентификатор

    Она заставляет препроцессор "забыть" макроопределение с этим идентификатором. Если указанный идентификатор не является определенным в данный момент макроименем, то команда #undef игнорируется.

    R.16.4 Включение файлов

    Управляющая строка вида:

    #include ‹имяфайла›

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

    Аналогично, управляющая строка вида:

    #include "имяфайла"

    приводит к замене данной строки на содержимое файла с указанным именем. Поиск этого файла начинается в особых (системных) частях архива, указанных в начале последовательности поиска. Если там он не найден, то поиск файла идет по всей последовательности, как если бы управляющая строка имела вид:

    #include ‹имяфайла›

    В имени файла, ограниченном символами ‹ и › нельзя использовать символы конца строки или ›. Если в таком имени появится один из символов ', \, или ", а также последовательность символов /* или //, то результат считается неопределенным.

    В имени файла, ограниченном парой символов " нельзя использовать символы конца строки или ", хотя символ › допустим. Если в таком имени появится символ ' или \ или последовательность /* или //, то результат считается неопределенным.

    Если команда

    #include строка-лексем

    имеет вид, соответствующий ни первой, ни второй управляющей строке, то лексемы препроцессора, заданные в этой команде обрабатываются как обычный текст. В результате должна получиться команда, вид которой соответствует одному из приведенных. Она и будет выполнена как положено.

    Команда #include может быть в файле, который сам появился в результате выполнения другой команды #include.

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

    R.16.5 Условная трансляция

    С помощью препроцессора можно организовать условную трансляцию программы. Синтаксически это задается следующим образом:

    условное:

     часть-if части-elif opt часть-else opt строка-endif

    часть-if:

     строка-if текст

    строка-if:

     # if выражение-константа

     # ifdef идентификатор

     # ifndef идентификатор

    части-elif:

     строка-elif текст

     части-elif строка-elif текст

    строка-elif:

     # elif выражение-константа

    часть-else:

     строка-else текст

    строка-else:

     # else

    строка-endif:

     # endif

    Константные выражения в #if и #elif (если эти части есть) вычисляются в порядке их задания в тексте до тех пор, пока одно из них не окажется отличным от нуля. Операторы С++, следующие за строкой, в которой выражение оказалось равным нулю, не транслируются. Команды препроцессора, идущие за этой строкой игнорируются. После того, как найдена команда с ненулевым значением выражения, текст всех последующих частей #elif и #else (т.е. операторы C++ и команды препроцессора) игнорируется. Текст, относящийся к первой команде с ненулевым значением выражения подлежит обычной препроцессорной обработке и трансляции. Если значения всех выражений, указанных в #if и #elif, оказались равными нулю, тогда обычной обработке подлежит текст, относящийся к #else.

    В выражении-константе, которое встретилось в #if или #elif можно использовать унарную операцию defined, причем в двух вариантах:

    defined идентификатор

    или

    defined (идентификатор)

    Если эта операция применяется к идентификатору, который был определен с помощью команды #define, и если это определение не было отменено командой #undef, то результат равен 1, иначе результат равен 0. Сам идентификатор defined нельзя переопределить, нельзя и отменить его определение.

    После применения операций defined происходит раскрытие всех всех макроопределений, имеющихся в константном выражении см. §R.16.3. В результате должно получиться целочисленное выражение-константа, отличающееся от определения в §R.5.19 тем, что типы int и unsigned int рассматриваются как long и unsigned long соответственно, а кроме того в этом выражении не должно быть операций приведения, sizeof или элемента перечисления.

    Управляющая строка

    #ifdef идентификатор

    эквивалентна строке

    #if defined идентификатор

    а управляющая строка

    #ifndef идентификатор

    эквивалентна строке

    #if !defined идентификатор

    Конструкции, задающие условную трансляцию, могут быть вложенными, но реализация может накладывать ограничение на глубину вложенности этих конструкций.

    R.16.6 Управление строками

    Для удобства написания программ, порождающих текст на С++, введена управляющая строка вида:

    #line константа "имяфайла" opt

    Она задает значение предопределенному макроимени __LINE__ (§R.16.10), которое используется в диагностических сообщениях или при символической отладке; а именно: номер следующей строки входного текста считается равным заданной константе, которая должна быть десятичным целым числом. Если задано "имяфайла", то значение макроимени __FILE__ (§R.16.10) становится равным имени указанного файла. Если оно не задано, __FILE__ не меняет своего значения.

    Макроопределения в этой управляющей строке раскрываются до выполнения самой команды.

    R.16.7 Команда error

    Строка вида:

    #error строка-лексем

    заставляет реализацию выдать диагностическое сообщение, состоящее из заданной последовательности лексем препроцессора.

    R.16.8 Команда pragma

    Строка вида:

    #pragma строка-лексем

    заставляет реализацию вести себя некоторым определенным образом при условии что реализация "понимает" эту строку. Любая нераспознанная строка #pragma игнорируется.

    R.16.9 Пустая директива

    Команда препроцессора вида

    #

    не оказывает никакого действия.

    R.16.10 Предопределенные макроимена

    В процессе трансляции определенную информацию содержат следующие предопределенные макроимена.

    __LINE__ десятичная константа, содержащая номер текущей строки текста программы на С++
    __FILE__ строка литералов, представляющая имя транслируемого входного файла
    __DATE__ строка литералов, представляющая дату трансляции в виде "Mmm dd yyyy" или "Mmm d yyyy", если число меньше 10, (здесь Mmm задает месяц, dd - день, а yyyy - год)
    __TIME__ строка литералов, представляющая время трансляции в виде "hh:mm:ss", (здесь hh задает часы, mm - минуты, а ss - секунды)

    Кроме того, считается определенным при трансляции программы на C++ макроимя __cplusplus.

    Перечисленные макроимена нельзя как переопределять, так и отменять их определения.

    Макроимена __LINE__ и __FILE__ можно определить с помощью команды #line (§R.16.6).

    Определено ли макроимя __STDC, и если да, то каково его значение, зависит от реализации.








    Главная | В избранное | Наш E-MAIL | Прислать материал | Нашёл ошибку | Наверх