stringtranslate.com

Препроцессор C

Препроцессор C — это препроцессор макросов для нескольких языков программирования , таких как C , Objective-C , C++ и различных языков Fortran . Препроцессор обеспечивает включение заголовочных файлов , расширение макросов , условную компиляцию и управление строками.

Язык директив препроцессора лишь слабо связан с грамматикой C и поэтому иногда используется для обработки других типов текстовых файлов . [1]

История

Препроцессор был представлен в C примерно в 1973 году по настоянию Алана Снайдера, а также в знак признания полезности механизмов включения файлов, доступных в BCPL и PL/I . Его первоначальная версия предлагала только включение файлов и простую замену строк с использованием #includeи #defineдля макросов без параметров соответственно. Вскоре после этого он был расширен сначала Майком Леском , а затем Джоном Райзером, чтобы включить макросы с аргументами и условную компиляцию. [2]

Препроцессор C был частью давней традиции макроязыков в Bell Labs, начатой ​​Дугласом Иствудом и Дугласом Макилроем в 1959 году . [3]

Фазы

Предварительная обработка определяется первыми четырьмя (из восьми) фазами трансляции, указанными в стандарте C.

  1. Замена триграфа: препроцессор заменяет последовательности триграфов символами, которые они представляют. Эта фаза будет удалена в C23 , следуя инструкциям C++17 .
  2. Объединение строк: строки физического источника, которые продолжаются экранированными последовательностями новой строки , объединяются в логические строки.
  3. Токенизация: препроцессор разбивает результат на токены предварительной обработки и пробелы . Он заменяет комментарии пробелами.
  4. Расширение макросов и обработка директив: выполняются строки директив предварительной обработки, включая включение файлов и условную компиляцию. Препроцессор одновременно расширяет макросы и, начиная с версии стандарта C 1999 года, обрабатывает _Pragmaоператоры.

Включая файлы

Одним из наиболее распространенных вариантов использования препроцессора является включение другого исходного файла:

#include <stdio.h> int main ( void ) { printf ( «Привет, мир! \n » ); вернуть 0 ; }    

Препроцессор заменяет строку #include <stdio.h>текстовым содержимым файла stdio.h, в котором, среди прочего, объявляется printf() функция .

Это также можно записать с использованием двойных кавычек, например #include "stdio.h". Если имя файла заключено в угловые скобки, поиск файла осуществляется по стандартным путям включения компилятора. Если имя файла заключено в двойные кавычки, путь поиска расширяется и включает текущий каталог исходного файла. Компиляторы C и среды программирования имеют возможность, позволяющую программисту определять, где можно найти включаемые файлы. Это можно ввести с помощью флага командной строки, который можно параметризовать с помощью makefile , чтобы, например, для разных операционных систем можно было заменять разные наборы включаемых файлов.

По соглашению, включаемые файлы имеют расширение .hили .hpp. Однако нет никаких требований, чтобы это соблюдалось. Файлы с .defрасширением могут обозначать файлы, предназначенные для многократного включения, каждый раз расширяя один и тот же повторяющийся контент; #include "icon.xbm"скорее всего, относится к файлу образа XBM (который одновременно является исходным файлом C).

#includeчасто вынуждает использовать #includeограждения или #pragma onceпредотвращать двойное включение.

Условная компиляция

Директивы if-else#if , #ifdef, , , и могут использоваться для условной#ifndef компиляции . и являются простыми сокращениями для и .#else#elif#endif#ifdef#ifndef#if defined(...)#if !defined(...)

#if VERBOSE >= 2 printf ( «трассировка сообщения» ); #endif 

Большинство компиляторов, предназначенных для Microsoft Windows, неявно определяют _WIN32. [4] Это позволяет компилировать код, включая команды препроцессора, только для систем Windows. Вместо этого определяют несколько компиляторов WIN32. Для таких компиляторов, которые не определяют _WIN32макрос неявно, его можно указать в командной строке компилятора, используя -D_WIN32.

#ifdef __unix__ /* __unix__ обычно определяется компиляторами, ориентированными на системы Unix */ # include <unistd.h> #elif define _WIN32 /* _WIN32 обычно определяется компиляторами, ориентированными на 32 или 64-битные системы Windows */ # include <windows.h > #эндиф    

__unix__В примере кода проверяется, определен ли макрос . Если да, <unistd.h>то файл включается. В противном случае он проверяет _WIN32, определен ли вместо этого макрос . Если да, <windows.h>то файл включается.

В более сложном #ifпримере можно использовать операторы; например:

#if !(определено __LP64__ || определено __LLP64__) || определено _WIN32 && !defined _WIN64 // компилируем для 32-битной системы #else // компилируем для 64-битной системы #endif

Трансляцию также можно вызвать сбоем с помощью #errorдирективы:

#if RUBY_VERSION == 190 #ошибка 1.9.0 не поддерживается #endif

Определение и расширение макроса

Существует два типа макросов: объектные и функциональные . Объектноподобные макросы не принимают параметров; макросы, подобные функциям (хотя список параметров может быть пустым). Общий синтаксис объявления идентификатора как макроса каждого типа соответственно следующий:

#define <идентификатор> <список токенов замены> // макрос типа объекта #define <идентификатор>(<список параметров>) <список токенов замены> // макрос типа функции, параметры примечания

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

Определение макроса можно удалить с помощью #undef:

#undef <идентификатор> // удаляем макрос

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

Объектноподобные макросы традиционно использовались как часть хорошей практики программирования для создания символических имен констант; например:

#define PI 3.14159

вместо жесткого кодирования чисел по всему коду. Альтернативой как в C, так и в C++, особенно в ситуациях, когда требуется указатель на число, является применение квалификатора constк глобальной переменной. Это приводит к тому, что значение сохраняется в памяти, а не заменяется препроцессором.

Пример функционального макроса:

#define RADTODEG(x) ((x) * 57.29578)

Это определяет преобразование радиан в градусы, которое можно вставить в код там, где это необходимо; например, RADTODEG(34). Это расширяется на месте, поэтому повторное умножение на константу не отображается по всему коду. Макрос здесь написан заглавными буквами, чтобы подчеркнуть, что это макрос, а не скомпилированная функция.

Второе xзаключено в собственную пару круглых скобок, чтобы избежать возможности неправильного порядка операций , когда это выражение, а не одно значение. Например, выражение правильно расширяется как ; без круглых скобок отдает приоритет умножению.RADTODEG(r + 1)((r + 1) * 57.29578)(r + 1 * 57.29578)

Аналогично, внешняя пара круглых скобок поддерживает правильный порядок действий. Например, расширяется до ; без круглых скобок отдает приоритет разделению.1 / RADTODEG(r)1 / ((r) * 57.29578)1 / (r) * 57.29578

Порядок расширения

Функциональное расширение макроса происходит в следующие этапы:

  1. Операции строкообразования заменяются текстовым представлением списка замены их аргументов (без выполнения расширения).
  2. Параметры заменяются списком их замены (без выполнения расширения).
  3. Операции конкатенации заменяются объединенным результатом двух операндов (без расширения результирующего токена).
  4. Токены, происходящие из параметров, расширяются.
  5. Полученные токены расширяются как обычно.

Это может привести к удивительным результатам:

#define HE HI #define LLO _THERE #define HELLO "HI THERE" #define CAT(a,b) a##b #define XCAT(a,b) CAT(a,b) #define CALL(fn) fn(HE, LLO) CAT ( HE , LLO ) // «HI THERE», потому что конкатенация происходит до нормального расширения XCAT ( HE , LLO ) // HI_THERE, потому что токены, происходящие из параметров («HE» и «LLO»), расширяются первым CALL ( CAT ) // «Привет», потому что это равно CAT(a,b)     

Специальные макросы и директивы

Определенные символы должны быть определены реализацией во время предварительной обработки. К ним относятся __FILE__и __LINE__, предопределенные самим препроцессором, которые расширяются до текущего файла и номера строки. Например, следующее:

// отладка макросов, чтобы мы могли сразу определить происхождение сообщения // это плохо #define WHERESTR "[file %s, строка %d]: " #define WHEEARRG __FILE__, __LINE__ #define DEBUGPRINT2(...) fprintf(stderr , __VA_ARGS__) #define DEBUGPRINT(_fmt, ...) DEBUGPRINT2(WHERESTR _fmt, WHEEARRG, __VA_ARGS__) // ИЛИ // хорошо #define DEBUGPRINT(_fmt, ...) fprintf(stderr, "[file %s, line %d]: " _fmt, __FILE__, __LINE__, __VA_ARGS__) DEBUGPRINT ( "эй, x=%d \n " , x ); 

печатает значение x, которому предшествует номер файла и номер строки, в поток ошибок, обеспечивая быстрый доступ к тому, на какой строке было создано сообщение. Обратите внимание, что WHERESTRаргумент объединяется со следующей за ним строкой. Значениями __FILE__и __LINE__можно манипулировать с помощью #lineдирективы. Директива #lineопределяет номер строки и имя файла строки ниже. Например:

#line 314 "pi.c" printf ( "line=%d file=%s \n " , __LINE__ , __FILE__ );  

генерирует printfфункцию:

printf ( "line=%d file=%s \n " , 314 , "pi.c" );  

Отладчики исходного кода также обращаются к позиции исходного кода, определенной с помощью __FILE__и __LINE__. Это позволяет отлаживать исходный код, когда C используется в качестве целевого языка компилятора, для совершенно другого языка. В первом стандарте C указано, что макросу __STDC__присваивается значение 1, если реализация соответствует стандарту ISO, и значение 0 в противном случае, а макрос __STDC_VERSION__определяется как числовой литерал, определяющий версию стандарта, поддерживаемую реализацией. Стандартные компиляторы C++ поддерживают этот __cplusplusмакрос. Компиляторы, работающие в нестандартном режиме, не должны устанавливать эти макросы или должны определять другие, чтобы сигнализировать о различиях.

Другие стандартные макросы включают __DATE__текущую дату и __TIME__текущее время.

Во втором издании стандарта C, C99 , добавлена ​​поддержка __func__, которая содержит имя определения функции, в которой она содержится, но поскольку препроцессор не зависит от грамматики C, это должно быть сделано в самом компиляторе с использованием переменная, локальная для функции.

Макросы, которые могут принимать различное количество аргументов ( вариативные макросы ), не разрешены в C89, но были введены рядом компиляторов и стандартизированы в C99 . Макросы с переменным числом параметров особенно полезны при написании оболочек для функций, принимающих переменное количество параметров, таких как printf, например, при регистрации предупреждений и ошибок.

Один малоизвестный шаблон использования препроцессора C известен как X-Macros . [5] [6] [7] X-макрос — это файл заголовка . Обычно они используют расширение .defвместо традиционного .h. Этот файл содержит список похожих вызовов макросов, которые можно назвать «макросами компонентов». Затем на включаемый файл ссылаются неоднократно.

Многие компиляторы определяют дополнительные нестандартные макросы, хотя они зачастую плохо документированы. Общей ссылкой на эти макросы является проект «Предварительно определенные макросы компилятора C/C++», в котором перечислены «различные предварительно определенные макросы компилятора, которые можно использовать для идентификации стандартов, компиляторов, операционных систем, аппаратных архитектур и даже базовых библиотек времени выполнения». во время компиляции».

Стрингификация токена

Оператор #(известный как оператор строкообразования или оператор строкообразования ) преобразует токен в строковый литерал C , соответствующим образом экранируя любые кавычки или обратную косую черту.

Пример:

#define str(s) #sstr ( p = "foo \n " ;) // выводит "p = \"foo\\n\";" str ( \ n ) // выводит "\n"    

Если требуется строковое расширение аргумента макроса, необходимо использовать два уровня макросов:

#define xstr(s) str(s) #define str(s) #s #define foo 4str ( foo ) // выводит "foo" xstr ( foo ) // выводит "4"    

Аргумент макроса нельзя объединить с дополнительным текстом и затем преобразовать в строку. Однако можно записать ряд констант соседних строк и строковых аргументов: компилятор C затем объединит все константы соседних строк в одну длинную строку.

Конкатенация токенов

Оператор ##(известный как «Оператор вставки токена») объединяет два токена в один токен.

Пример:

#define DECLARE_STRUCT_TYPE(name) typedef имя структуры##_s name##_tDECLARE_STRUCT_TYPE ( g_object ); // Выходы: typedef struct g_object_s g_object_t; 

Пользовательские ошибки компиляции

Директива #errorвыводит сообщение через поток ошибок.

#error "сообщение об ошибке"

Включение двоичных ресурсов

C23 представит #embedдирективу для включения двоичных ресурсов. [8] Это позволяет включать в программу двоичные файлы (например, изображения), не являющиеся действительными исходными файлами C (например, XBM), без необходимости обработки внешними инструментами, такими как xxd -iи без использования строковых литералов , которые имеют ограничение длины в MSVC . . Аналогично xxd -iдиректива заменяется разделенным запятыми списком целых чисел, соответствующих данным указанного ресурса. Точнее, если массив типа unsigned charинициализируется с использованием #embedдирективы, результат будет таким же, как если бы ресурс был записан в массив с помощью fread(если параметр не меняет ширину элемента внедрения на что-то отличное от CHAR_BIT). Помимо удобства, #embedс ним также проще обращаться компиляторам, поскольку им разрешено пропускать раскрытие директивы до ее полной формы из-за правила «как если бы» .

Встраиваемый файл можно указать так же, как и #include, то есть либо между шевронами , либо между кавычками. Директива также позволяет передавать ей определенные параметры для настройки ее поведения, которые следуют за именем файла. Стандарт C определяет следующие параметры, а реализации могут определять свои собственные. Параметр limitиспользуется для ограничения ширины включаемых данных. В основном он предназначен для использования с «бесконечными» файлами, такими как urandom . Параметры prefixи suffixпозволяют программисту указать префикс и суффикс для внедренных данных, которые используются тогда и только тогда, когда внедренный ресурс не пуст. Наконец, if_emptyпараметр заменяет всю директиву, если ресурс пуст (что происходит, если файл пуст или указано ограничение 0). Все стандартные параметры также могут быть окружены двойными подчеркиваниями, как и стандартные атрибуты в C23, например, __prefix__взаимозаменяемы с prefix. Параметры, определенные реализацией, используют форму, аналогичную синтаксису атрибута (например, vendor::attr), но без квадратных скобок. Хотя все стандартные параметры требуют передачи аргумента (например, для ограничения требуется ширина), это обычно необязательно, и даже набор круглых скобок можно опустить, если аргумент не требуется, что может иметь место в некоторых реализациях. определенные параметры.

Реализации

Все реализации C, C++ и Objective-C предоставляют препроцессор, поскольку предварительная обработка является обязательным шагом для этих языков, а его поведение описывается официальными стандартами для этих языков, такими как стандарт ISO C.

Реализации могут иметь собственные расширения и отклонения и различаться по степени соответствия письменным стандартам. Их точное поведение может зависеть от флагов командной строки, предоставляемых при вызове. Например, препроцессор GNU C можно сделать более совместимым со стандартами, предоставив определенные флаги. [9]

Функции препроцессора, специфичные для компилятора

Эта #pragmaдиректива является директивой, специфичной для компилятора , которую поставщики компиляторов могут использовать в своих целях. Например, a #pragmaчасто используется для подавления определенных сообщений об ошибках, управления отладкой кучи и стека и т. д. Компилятор с поддержкой библиотеки распараллеливания OpenMP может автоматически распараллеливать forцикл с помощью #pragma omp parallel for.

В C99 появилось несколько стандартных #pragmaдиректив вида #pragma STDC ..., которые используются для управления реализацией чисел с плавающей запятой. _Pragma(...)Также была добавлена ​​альтернативная макроподобная форма .

Функции препроцессора, зависящие от языка

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

Другое использование

Поскольку препроцессор C можно вызывать отдельно от компилятора, с которым он поставляется, его можно использовать отдельно на разных языках. Яркие примеры включают его использование в ныне устаревшей системе imake и для предварительной обработки Fortran . Однако такое использование в качестве препроцессора общего назначения ограничено: язык ввода должен быть в достаточной степени C-подобным. [9] Компилятор GNU Fortran автоматически вызывает «традиционный режим» (см. ниже) cpp перед компиляцией кода Fortran, если используются определенные расширения файлов. [16] Intel предлагает препроцессор Fortran, fpp, для использования с компилятором ifort , который имеет аналогичные возможности. [17]

CPP также приемлемо работает с большинством языков ассемблера и языков, подобных Алголу. Для этого необходимо, чтобы синтаксис языка не конфликтовал с синтаксисом CPP, что означает отсутствие строк, начинающихся с, #и чтобы двойные кавычки, которые cpp интерпретирует как строковые литералы и, следовательно, игнорирует, не имеют другого синтаксического значения. «Традиционный режим» (действующий как препроцессор до ISO C), как правило, более разрешителен и лучше подходит для такого использования. [18]

Препроцессор C не является полным по Тьюрингу , но он очень близок к этому: можно указать рекурсивные вычисления, но с фиксированной верхней границей объема выполняемой рекурсии. [19] Однако препроцессор C не предназначен для использования в качестве языка программирования общего назначения и не работает в качестве языка программирования общего назначения. Поскольку препроцессор C не имеет функций некоторых других препроцессоров, таких как рекурсивные макросы, выборочное расширение в соответствии с кавычками и вычисление строк в условных выражениях, он очень ограничен по сравнению с более общим макропроцессором, таким как m4 .

Смотрите также

Рекомендации

  1. ^ Предварительная обработка текста общего назначения с помощью препроцессора C. Использование JavaScript
  2. ^ Ричи (1993)
  3. ^ «Bell SAP – SAP с условными и рекурсивными макросами» . HOPL: Интернет-историческая энциклопедия языков программирования .
  4. ^ Список предопределенных макросов реализации ANSI C и Microsoft C++.
  5. ^ Вирзениус, Ларс. C «Прием препроцессора для реализации подобных типов данных». Проверено 9 января 2011 г.
  6. ^ Мейерс, Рэнди (май 2001 г.). «Новые макросы C:X». Журнал доктора Добба . Проверено 1 мая 2008 г.
  7. ^ Бил, Стефан (август 2004 г.). «Супермакросы» . Проверено 27 октября 2008 г. {{cite journal}}: Требуется цитировать журнал |journal=( помощь )
  8. ^ «WG14-N3017: #embed — сканируемый, удобный для инструментов механизм включения двоичных ресурсов» . open-std.org . 27 июня 2022 года. Архивировано из оригинала 24 декабря 2022 года.
  9. ^ ab «Препроцессор C: обзор» . Проверено 17 июля 2016 г.
  10. ^ «WG14-N3096: Проект ISO/IEC 9899:2023» (PDF) . open-std.org . 1 апреля 2023 г. Архивировано (PDF) из оригинала 2 апреля 2023 г.
  11. ^ «Рабочий проект стандарта языка программирования C++» (PDF) . 22 марта 2023 г.
  12. ^ Устаревшие функции GCC
  13. ^ «Заголовки оберток (препроцессор C)» .
  14. ^ «N4720: Рабочий проект, расширения C++ для модулей» (PDF) . Архивировано (PDF) из оригинала 30 апреля 2019 г.
  15. ^ «P1857R1 - Обнаружение зависимостей модулей» .
  16. ^ «1.3 Предварительная обработка и условная компиляция». Проект ГНУ.
  17. ^ «Использование препроцессора fpp». Интел . Проверено 14 октября 2015 г.
  18. ^ «Обзор (препроцессор C)» . gcc.gnu.org . Сказав это, вам часто может сойти с рук использование cpp для вещей, которые не являются C. Другие языки программирования, подобные Алголу, часто безопасны (Ada и т. д.). Как и ассемблер, но с осторожностью. Режим -traditional-cpp сохраняет больше пробелов и в остальном является более разрешительным. Многих проблем можно избежать, написав комментарии в стиле C или C++ вместо комментариев на родном языке и сохранив простоту макросов.
  19. ^ «Завершен ли препроцессор C99 Turing?». Архивировано из оригинала 24 апреля 2016 года.

Источники

Внешние ссылки