PVOID - Директивы препроцессора

Директивы препроцессора

Директивы препроцессора — это строки, включаемые в код программы, которым предшествует символ решетки (#). Эти строки являются не выражениями программы, а директивами препроцессора. Препроцессор проверяет код до начала компиляции кода и заменяет все эти директивы на обычные выражения до того, как какой-либо код будет сгенерирован.
Директивы препроцессора распостраняются только на одну строку кода. Как только будет встречен символ перевода строки, директива препроцессора завершится. Символ точки с запятой (;) не ожидается в конце директивы препроцессора. Единственный способ, которым директива препроцессора может быть расширена более, чем на одну строку, — это добавление символа обратного слеша (\) в конец строки перед символом перевода строки.

Определение макроса (#define, #undef)


 Для определения макроса препроцессора можно использовать #define.

#define PI 3.14

Когда препроцессор встречает эту директиву, он заменяет каждое вхождение PI во всём остальном коде на 3.14 . В качестве замены можно использовать не только числа, но и выражения, блоки и что угодно еще. Препроцессор не понимает языка C++, он просто заменяет каждое вхождение идентификатора на другое значение, в данном примере заменяет PI на 3.14 .

#define TABLE_SIZE 100
int table1[TABLE_SIZE];
int table2[TABLE_SIZE];

После того, как препроцессор заменит TABLE_SIZE, код станет эквивалентен следующему:

int table1[100];
int table2[100];

#define также может работать с параметрами для определения макросов функций:

#define getmax(a,b) a>b?a:b

Это заменит каждое вхождение getmax с двумя аргументами указанным выражением, заменяя также каждый аргумент по его идентификатору так же, как если бы это была функция:

// макрос функции
#include <iostream>
using namespace std;

#define getmax(a,b) ((a)>(b)?(a):(b))

int main()
{
  int x=5, y;
  y= getmax(x,2);
  cout << y << endl;
  cout << getmax(7,x) << endl;
  return 0;
}
5
7

Определения макросов не зависят от структуры блоков. Макрос длится до тех пор, пока не будет отменён директивой препроцессора #undef:

#define TABLE_SIZE 100
int table1[TABLE_SIZE];
#undef TABLE_SIZE
#define TABLE_SIZE 200
int table2[TABLE_SIZE];

Это сгенерирует код, который будет эквивалентен следующему:

int table1[100];
int table2[200];

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

#define str(x) #x
cout << str(test);

транслируется в:

cout << "test";

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

 Условная компиляция (#ifdef, #ifndef, #if, #endif, #else and #elif)


Эти директивы позволяют включать или отбрасывать часть кода программы при выполнении определённого условия.
#ifdef позволяет скомпилировать часть кода, только если определён макрос, который указан как параметр, независимо от его значения. Например:

#ifdef TABLE_SIZE
int table[TABLE_SIZE];
#endif

В этом случае строка кода int table[TABLE_SIZE]; будет скомпилирована, только если макрос TABLE_SIZE был ранее определён посредством #define, независимо от значения TABLE_SIZE. Если макрос не был определён, эта строка кода будет проигнорирована при компиляции программы.

#ifndef служит обратной цели: код между директивами #ifndef и #endif компилируется, только если указанный макрос ранее не был определён. Например:

#ifndef TABLE_SIZE
#define TABLE_SIZE 100
#endif
int table[TABLE_SIZE];

В этом случае если при достижении этой части кода макрос TABLE_SIZE ещё не был определён, то он будет определён значением 100. Если он уже был определён ранее, то он сохранит своё предыдущее значение, потому что директива #define будет проигнорирована.

Директивы #if, #else и #elif (т.е. «else if») служат, чтобы указать какое-то условие, которое должно быть выполнено для того, чтобы часть кода, которую они окружают, была скомпилирована. Условие, следующее за #if или #elif, может быть только константным выражением, включая макросы. Например:

#if TABLE_SIZE>200
#undef TABLE_SIZE
#define TABLE_SIZE 200
 
#elif TABLE_SIZE<50
#undef TABLE_SIZE
#define TABLE_SIZE 50
 
#else
#undef TABLE_SIZE
#define TABLE_SIZE 100
#endif
 
int table[TABLE_SIZE];

Обратите внимание, что вся структура связанных директив #if, #elif и #else заканчивается на #endif.
Поведение #ifdef и #ifndef также может быть достигнуто с помощью специальных операторов defined и !defined соответственно в любой директиве #if или #elif:

#if defined ARRAY_SIZE
#define TABLE_SIZE ARRAY_SIZE
#elif !defined BUFFER_SIZE
#define TABLE_SIZE 128
#else
#define TABLE_SIZE BUFFER_SIZE
#endif 

Управление строками (#line)


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

Директива #line позволяет управлять и тем и другим: номерами строк в файлах с исходным кодом, а также именем файла, которое должно отображаться при возникновении ошибки. Её формат следующий:

#line number "filename"

Где number — это новый номер строки, который будет присвоен следующей строке кода. С этого момента номера последующих строк будут последовательно увеличиваться. "filename" — опциональный параметр, который позволяет переопределить отображаемое имя файла. Например:

#line 20 "assigning variable"
int a?;

Этот код создаст ошибку, которая будет отображена как error in file "assigning variable", line 20.

Директива #error


Эта директива прерывает процесс компиляции при ее обнаружении, генерируя ошибку компиляции, которая может быть указана в качестве параметра:

#ifndef __cplusplus
#error A C++ compiler is required!
#endif

В этом примере процесс компиляции прерывается, если макрос __cplusplus не определён (этот макрос определён по умолчанию во всех компиляторах C++).

Включение исходных файлов (#include)


Эта директива активно используется в других разделах этого руководства. Когда препроцессор находит директиву #include, он замещает её всем содержимым указанного заголовка или файла. Есть два способа использовать #include:

#include <header>
#include "file"

В первом случае заголовок указывается в угловых скобках <>. Такой способ используется для включения заголовков, определяемых реализацией компилятора или библиотек, таких как заголовки, составляющие стандартную библиотеку (iostream,string,...). 

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

Синтаксис, используемый во втором #include использует двойные кавычки и включает file. Способ поиска file определяется реализацией компилятора, но обычно включает текущую директорию. В случае, если file не найден, компилятор интерпретирует его как включение заголовка, как будто двойные кавычки ("") заменены на угловые скобки (<>).

Директива #pragma


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

Если компилятор не поддерживает какой-либо аргумент для #pragma, он игнорируется — синтаксическая ошибка не генерируется.

Предопределённые макросы


Следующие имена макросов всегда определены (все они начинаются и заканчиваются двумя подчёркиваниями, __):

 Макрос   Значение  
 __LINE__   Целочисленное значение, равное номеру текущей строки в исходном коде 
 __FILE__   Строковый литерал, содержащий предполагаемое имя компилируемого исходного файла 
 __DATE__   Строковый литерал в формате «Ммм дд гггг», содержащий дату, в которую был начат процесс компиляции 
 __TIME__   Строковый литерал в формате «чч:мм:сс», содержащий время, когда был начат процесс компиляции 
 __cplusplus   Целочисленное значение. Эта константа определена во всех компиляторах C++ некоторым значением. Это значение зависит от версии стандарта языка C++, поддерживаемого компилятором:
199711L : < C++11
201103L : C++11
201402L : C++14
201703L : C++17
202002L : C++20
202302L : C++23
Несоответствующие компиляторы определяют эту константу как некоторое значение длиной не более пяти цифр. Обратите внимание, что многие компиляторы не полностью соответствуют требованиям и могут не  определять эту константу ни как одно из указанных выше значений. 
 __STDC_HOSTED__   Расширяется в целочисленную константу 1, если реализация выполняется из операционной системы (со всеми доступными стандартными заголовками), 0 — если выполняется без операционной системы.

 

Следующие макросы определены опционально, обычно в зависимости от доступной функциональности:

 Макрос Значение 
__STDC__  в C: если значение равно 1, то реализация соответствует стандарту C
 в C++: зависит от реализации
__STDC_VERSION__  в C:
расширяется в целочисленную константу типа long, значение которого увеличивается с каждой версией стандарта C
199409L: C95
199901L: C99
201112L: C11
201710L: C17
202311L: C23
 в C++: зависит от реализации
__STDC_MB_MIGHT_NEQ_WC__  1 если многобайтовая кодировка может задать символу другое значение в символьных литералах
__STDC_ISO_10646__  Значение в форме ггггммL, указывающее дату применения стандарта Unicode, за которым следует кодировка символов wchar_t
__STDCPP_STRICT_POINTER_SAFETY__  1 если реализация имеет строгую безопасность указателя (см. get_pointer_safety)
__STDCPP_THREADS__  1 если в программе может быть более одного потока

 

Конкретная реализация может определять дополнительные константы.

Например:

// standard macro names
#include <iostream>
using namespace std;

int main()
{
  cout << "This is the line number " << __LINE__;
  cout << " of file " << __FILE__ << ".\n";
  cout << "Its compilation began " << __DATE__;
  cout << " at " << __TIME__ << ".\n";
  cout << "The compiler gives a __cplusplus value of " << __cplusplus;
  return 0;
}
This is the line number 7 of file /home/jay/stdmacronames.cpp.
Its compilation began Nov  1 2005 at 10:12:29.
The compiler gives a __cplusplus value of 1

.