Богослужебные книги
- 1 year ago
- 0
- 0
|
Эту статью необходимо
исправить в соответствии с
правилом Википедии об оформлении статей
.
|
Препроцессор C/C++ ( англ. pre processor , предобработчик) — программа , подготавливающая код программы на языке C / C++ к компиляции .
Препроцессором выполняются следующие действия:
#
» и «
\
»;
#include
);
#define
);
#if
,
#ifdef
,
#elif
,
#else
,
#endif
);
#warning
,
#error
).
Условная компиляция позволяет выбрать код для компиляции в зависимости от:
Этапы работы препроцессора:
Язык препроцессора C/C++ не является полным по Тьюрингу хотя бы потому, что с помощью директив невозможно заставить препроцессор зависнуть. См. рекурсивная функция (теория вычислимости) .
Директивой (командной строкой) препроцессора называется строка в исходном коде, имеющая следующий формат:
#ключевое_слово параметры
:
#
;
Список ключевых слов:
define
— создание константы или
макроса
;
undef
— удаление константы или макроса;
include
— вставка содержимого указанного файла;
if
— проверка истинности выражения;
ifdef
— проверка существования константы или макроса;
ifndef
— проверка несуществования константы или макроса;
else
— ветка условной компиляции при ложности выражения
if
;
elif
— проверка истинности другого выражения; краткая форма записи для комбинации
else
и
if
;
endif
— конец ветки условной компиляции;
line
— указание имени файла и номера текущей строки для компилятора;
error
— вывод сообщения и остановка компиляции;
warning
— вывод сообщения без остановки компиляции;
pragma
— указание действия, зависящего от реализации, для препроцессора или компилятора;
|
В разделе
не хватает
ссылок на источники
(см.
рекомендации по поиску
).
|
При обнаружении директив
#include "..."
и
#include <...>
, где «…» — имя файла, препроцессор читает содержимое указанного файла, выполняет директивы и замены (подстановки), заменяет директиву
#include
на директиву
#line
и обработанное содержимое файла.
Для
#include "..."
поиск файла выполняется в текущей папке и папках, указанных в командной строке компилятора. Для
#include <...>
поиск файла выполняется в папках, содержащих файлы стандартной библиотеки (пути к этим папкам зависят от реализации компилятора).
При обнаружении директивы
#include последовательность-лексем
, не совпадающей ни с одной из предыдущих форм, рассматривает последовательность лексем как текст, который в результате всех макроподстановок должен дать
#include <...>
или
#include "..."
. Сгенерированная таким образом директива далее будет интерпретироваться в соответствии с полученной формой.
Включаемые файлы обычно содержат:
Директива
#include
обычно указывается в начале файла (в заголовке), поэтому включаемые файлы называются
заголовочными
.
Пример включения файлов из стандартной библиотеки языка C .
#include <math.h> // включение объявлений математических функций
#include <stdio.h> // включение объявлений функций ввода-вывода
Использование препроцессора считается неэффективным по следующим причинам:
Начиная с 1970-х годов стали появляться способы, заменившие включение файлов. В языках
Java
и
Common Lisp
используются пакеты (ключевое слово
package
) (см.
package в Java
), в языке
Паскаль
—
англ.
units
(ключевые слова
unit
и
uses
), в языках
Modula
,
OCaml
,
Haskell
и
Python
— модули. В языке
D
, разработанном для замены языков
C
и
C++
, используется ключевые слова
module
и
import
.
|
В разделе
не хватает
ссылок на источники
(см.
рекомендации по поиску
).
|
Константы и макросы препроцессора используются для определения небольших фрагментов кода.
// константа
#define BUFFER_SIZE 1024
// макрос
#define NUMBER_OF_ARRAY_ITEMS( array ) ( sizeof( array ) / sizeof( *(array) ) )
Каждая константа и каждый макрос заменяются соответствующим им определением. Макросы имеют параметры, похожи на функции, используются для уменьшения накладных расходов при вызове функций в случаях, когда небольшого кода, вызываемого функцией, достаточно для ощутимого снижения производительности.
Пример. Определение макроса max , принимающего два аргумента: a и b .
#define max( a, b ) ( (a) > (b) ? (a) : (b) )
Макрос вызывается так же, как и любая функция.
z = max( x, y );
После замены макроса код будет выглядеть следующим образом:
z = ( (x) > (y) ? (x) : (y) );
Однако, наряду с преимуществами использования макросов в языке Си, например, для определения обобщённых типов данных или отладочных инструментов, они также несколько снижают эффективность их применения и даже могут привести к ошибкам.
Например, если f и g — две функции, вызов
z = max( f(), g() );
не вычислит один раз f() и один раз g() , и поместит наибольшее значение в z , как этого можно было ожидать. Вместо этого одна из функций будет вычислена дважды. Если функция имеет побочные эффекты, то вероятно, что её поведение будет отличаться от ожидаемого.
Макросы Си могут походить на функции, создавая новый синтаксис в некоторых пределах, а также могут быть дополнены произвольным текстом (хотя компилятор Си требует, чтобы текст был без ошибок написанным Си-кодом или оформлен как комментарий), но у них есть некоторые ограничения как у программных конструкций. Макросы, схожие с функциями, например, могут быть вызваны как «настоящие» функции, но макрос не может быть передан другой функции при помощи указателя, по той причине, что макрос сам по себе не имеет адреса.
Некоторые современные языки обычно не используют такой способ метапрограммирования с использованием макросов как дополнений строк символов, в расчете или на автоматическое, или на ручное подключение функций и методов, а вместо этого - другие способы абстракции, такие как шаблоны , обобщённые функции или параметрический полиморфизм . В частности, позволяют избежать одного из главных недостатков макросов в современных версиях Си и C++, так как то, что встроенная функция обеспечивает преимущество макросов в снижении накладных расходов при вызове функции, но её адрес можно передавать в указателе для косвенных вызовов или использовать в качестве параметра. Аналогично, проблема множественных вычислений, упомянутая выше в макросе max , для встроенных функций неактуальна.
Константы #define можно заменить на enum, а макросы — на функции
inline
.
Эти операторы используются при создании макросов. Оператор # обрамляет параметр макроса в двойные кавычки, например:
#define make_str( bar ) # bar
printf( make_str( 42 ) );
препроцессор преобразует в:
printf( "42" );
Оператор ## в макросах объединяет две лексемы, например:
#define MakePosition( x ) x##X, x##Y, x##Width, x##Height
int MakePosition( Object );
препроцессор преобразует в:
int ObjectX, ObjectY, ObjectWidth, ObjectHeight;
1) Управляющая строка следующего вида заставляет препроцессор заменять идентификатор на последовательность лексем везде далее по тексту программы:
#define идентификатор последовательность_лексем
При этом символы пустого пространства в начале и в конце последовательности лексем выбрасываются. Повторная строка #define с тем же идентификатором считается ошибкой, если последовательности лексем не идентичны (несовпадения в символах пустого пространства не играют роли).
2) Строка следующего вида, где между первым идентификатором и открывающей круглой скобкой не должно быть символов пустого пространства, представляет собой макроопределение с параметрами, задаваемыми списком-идентификаторов.
#define идентификатор( список_идентификаторов ) последовательность_лексем
Как и в первой форме, символы пустого пространства в начале и в конце последовательности лексем выбрасываются, и макрос может быть повторно определен только с идентичным по количеству и именам списком параметров и с той же последовательностью лексем.
Управляющая строка следующего вида приказывает препроцессору «забыть» определение, данное идентификатору:
#undef идентификатор
Применение директивы #undef к не определенному ранее идентификатору не считается ошибкой.
{
На процесс подстановки влияют два специальных знака операций.
}
Восклицательным знаком (!) отмечены правила, отвечающие за рекурсивные вызов и определения.
#define cat( x, y ) х ## у
Вызов макроса «cat(var, 123)» будет заменён на «var123». Однако вызов «cat(cat(1, 2), 3)» не даст желаемого результата. Рассмотрим шаги работы препроцессора:
0: cat( cat( 1, 2 ), 3 ) 1: cat( 1, 2 ) ## 3 2: cat( 1, 2 )3
Операция «##» помешала правильному раскрытию аргументов второго вызова «cat». В результате получилась следующая цепочка лексем:
cat ( 1 , 2 )3
где «)3» — результат сцепления последней лексемы первого аргумента с первой лексемой второго аргумента, не является допустимой лексемой.
Можно задать второй уровень макроопределения в таком виде:
#define xcat( x, y ) cat( x, y )
Вызов «xcat(xcat(1, 2), 3)» будет заменён на «123». Рассмотрим шаги работы препроцессора:
0: xcat( xcat( 1, 2 ), 3 ) 1: сat( xcat( 1, 2 ), 3 ) 2: cat( cat( 1, 2 ), 3 ) 3: cat( 1 ## 2, 3 ) 4: cat( 12, 3 ) 5: 12 ## 3 6: 123
Всё прошло благополучно, потому что в раскрытии макроса «xcat» не участвовал оператор «##».
Многие статические анализаторы не умеют правильно обрабатывать макросы, поэтому качество статического анализа снижается [ источник не указан 2747 дней ] .
Константы, создаваемые препроцессором автоматически:
__LINE__
заменяется на номер текущей строки; номер текущей строки может быть переопределен директивой
#line
; используется для
отладки
;
__FILE__
заменяется на имя файла; имя файла тоже может быть переопределено с помощью директивы
#line
;
__FUNCTION__
заменяется на имя текущей функции;
__DATE__
заменяется на текущую дату (на момент обработки кода препроцессором);
__TIME__
заменяется на текущее время (на момент обработки кода препроцессором);
__TIMESTAMP__
заменяется на текущие дату и время (на момент обработки кода препроцессором);
__COUNTER__
заменяется на уникальное число, начиная от 0; после каждой замены число увеличивается на единицу;
__STDC__
заменяется на 1, если компиляция происходит в соответствии со стандартом языка C;
__STDC_HOSTED__
определена в C99 и выше; заменяется на 1, если выполнение происходит под управлением
ОС
;
__STDC_VERSION__
определена в C99 и выше; для C99 заменяется на число 199901, а для C11 — на число 201112;
__STDC_IEC_559__
определена в C99 и выше; константа существует, если компилятор поддерживает операции с числами с плавающей точкой по стандарту IEC 60559;
__STDC_IEC_559_COMPLEX__
определена в C99 и выше; константа существует, если компилятор поддерживает операции с комплексными числами по стандарту IEC 60559; стандарт C99 обязывает поддерживать операции с комплексными числами;
__STDC_NO_COMPLEX__
определена в C11; заменяется на 1, если не поддерживаются операции с комплексными числами;
__STDC_NO_VLA__
определена в C11; заменяется на 1, если не поддерживаются массивы переменной длины; в С99 массивы переменной длины обязательно должны поддерживаться;
__VA_ARGS__
определена в C99 и позволяет создавать макросы с переменным числом аргументов.
Препроцессор языка Си предоставляет возможность компиляции с условиями. Это допускает возможность существования различных версий одного кода. Обычно такой подход используется для настройки программы под платформу компилятора, состояние (отлаживаемый код может быть выделен в результирующем коде) или возможность проверки подключения файла строго один раз.
В общем случае, программисту необходимо использовать конструкцию типа:
# ifndef FOO_H
# define FOO_H
...(код заголовочного файла)...
# endif
Такая «защита макросов» предотвращает двойное подключение заголовочного файла путём проверки существования этого макроса, который имеет то же самое имя, что и заголовочный файл. Определение макроса FOO_H происходит, когда заголовочный файл впервые обрабатывается препроцессором. Затем, если этот заголовочный файл вновь подключается, FOO_H уже определен, в результате чего препроцессор пропускает полностью текст этого заголовочного файла.
То же самое можно сделать, включив в заголовочный файл директиву:
# pragma once
Условия препроцессора можно задавать несколькими способами, например:
# ifdef x
...
# else
...
# endif
или
# if x
...
# else
...
# endif
Этот способ часто используется в системных заголовочных файлах для проверки различных возможностей, определение которых может меняться в зависимости от платформы; например, библиотека Glibc использует макросы с проверкой особенностей с целью проверить, что операционная система и оборудование их (макросы) корректно поддерживает при неизменности программного интерфейса.
Большинство современных языков программирования не использует такие возможности, больше полагаясь на традиционные операторы условия
if...then...else...
, оставляя компилятору задачу извлечения бесполезного кода из компилируемой программы.
|
В разделе
не хватает
ссылок на источники
(см.
рекомендации по поиску
).
|
См. диграфы и триграфы в языках C/C++.
Препроцессор обрабатывает диграфы «
%:
» («
#
»), «
%:%:
» («
##
») и триграфы «
??=
» («
#
»), «
??/
» («
\
»).
Препроцессор считает последовательность « %:%: » двумя токенами при обработке кода C и одним токеном при обработке кода C++.
Для улучшения этой статьи по информационным технологиям
желательно
:
|