§ 13. Препроцессор

§ 13.1 Вставка содержимого

Мы уже пользовались разными директивами препроцессора. Даже в самом первом примере была одна. Рассмотрим самое главное (не будем рассматривать диграфы и триграфы).

Директива #include включает содержимое заголовочного файла в ваш файл. Угловые скобки нужны для включения стандартных заголовочных файлов, кавычки — ваших собственных и библиотечных.

#include <stdio.h>  // Стандартные заголовочные файлы.
#include "my_lib.h" // Заголовочные файлы библиотек и проекта.

§ 13.2 Константы и макросы

Также мы уже пользовались директивой #define для задания констант. Они называются макросами (макроопределениями).

#define SIZE 10u

У этой директивы есть и такая форма.

#define FOO

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

#define INC(x) (x) + 1
printf("%d", INC(6)); // 7

Внутри макросов можно делать переносы.

#define INC(x) \
(x) + 1

Определим макрос, умножающий два числа.

#define MUL(x, y) x * y

И получим неверный результат.

printf("%d", MUL(2 + 2, 2 + 1)); // 7, а не 12

Это получается потому, что макрос — простая текстовая подстановка. Посмотрите: 2 + 2 * 2 + 1, результат действительно 7. Добавим скобки, тогда всё будет в порядке. Это не единственный минус макросов, ещё вызовы не могут быть рекурсивными, а их отладка усложнена.

#define MUL(x, y) (x) * (y)

Определение идентификатора можно и отменить.

#undef FOOBAR

§ 13.3 Ошибки и предупреждения

Директива #error выводит сообщение (если оно указано) и останавливает компиляцию. Ниже, где объясняется условная компиляция, есть пример использования.

#error Error code 7

Директива #warning также выводит сообщение (если оно указано), но не останавливает компиляцию.

#warning Warning: possible loss of data
#pragma message ("Warning: possible loss of data") // Visual Studio.

Директива #pragma обозначает действия препроцессора или компилятора, которые зависят от конкретной реализации.

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

Существует ряд предопределённых макросов. С их помощью, к примеру, можно подставить в исходный код номер строки или путь к файлу с исходным кодом.

printf("%d\n", __LINE__); // Выводит номер строки в исходном файле.
printf("%s\n", __FILE__); // Выводит полный путь к исходному файлу.

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

§ 13.5 Операторы # и ##

Эти операторы используются при создании макросов. Оператор # перед параметром макроса добавляет кавычки.

#define STRING(bar) # bar
printf(STRING(42)); // printf("42");

Оператор ## в макросах объединяет две части (две лексемы) в одну.

#define POSITION(bar) int bar##_x, bar##_y
POSITION(foo); // int foo_x, foo_y;

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

Можно производить условную компиляцию (компиляцию одних частей программы и игнорирование других) с помощью директив #ifdef и #ifndef.

#ifdef FOO
// Если идентификатор FOO ранее был определён, то эта часть программы будет скомпилирована.
// Иначе этот код будет проигнорирован.
#endif
#ifndef FOO
// Если идентификатор FOO ранее не был определён, то эта часть программы будет скомпилирована.
// Иначе этот код будет проигнорирован.
#endif

С помощью директив #if, #elif и #else можно организовывать сложные проверки.

#define FOO 7

#if FOO == 7
    puts("Will work fine.");
#elif FOO < 7
    puts("Possible loss of data.");
#else
    #error Error code 7
#endif

Защита от повторного включения (include guards)

Теперь подробнее остановимся на самом распространённом случае условной компиляции — защите от повторного включения (когда директивы включения приводят к этому). Все объявления размещаются в одном файле, определения — в другом.

// my_lib.h

#ifndef __MY_LIB__
#define __MY_LIB__

// Объявления переменных, структур, функций и т. д.

#endif
// my_lib.c

#include "my_lib.h"

// Определения...

Директива #ifndef позаботится обо всём. Если файл включается впервые, то есть идентификатор __MY_LIB__ ещё не был определён, то он будет определён. А при последующих включениях файла весь код до #endif будет проигнорирован. Просто и элегантно.

Предлагаю самостоятельно найти в вашем компиляторе способ посмотреть результат работы препроцессора (удобно для отладки).

14 июня