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


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