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

Последнее обновление: 07.01.2023

Препроцессор является обязательным компонентом компилятора языка Си. Вообще весь процесс компиляции программы на языке Си разбивается на три этапа:

  1. Препроцессор

  2. Компиляция

  3. Линковка

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

Затем компилятор компилирует обработанный препроцессором исходный код в объектные файлы.

И на последнем этапе линкер (линковщик) объединяет (линкует) объектные файлы в один исполняемый файл или файл динамической библиотеки.

Для управления препроцессором применяются директивы, каждая из которых начинается с символа решетки # и располагается на отдельной строке. Препроцессор просматривает текст программы, находит эти директивы и должным образом обрабатывает их.

Мы можем использовать следующие директивы:

  • #define: определяет макрос или препроцессорный идентификатор

  • #undef: отменяет определение макроса или идентификатора

  • #ifdef: проверяет, определен ли идентификатор

  • #ifndef: проверяет неопределенности идентификатор

  • #include: включает текст из файла

  • #if: проверяет условие выражение (как условная конструкция if)

  • #else: задает альтернативное условие для #if

  • #endif: окончание условной директивы #if

  • #elif: задает альтернативное условие для #if

  • #line: меняет номер следующей ниже строки

  • #error: формирует текст сообщения об ошибке трансляции

  • #pragma: определяет действия, которые зависят от конкретной реализации компилятора

  • #: пустая директива, по сути ничего не делает

Рассмотрим основные из этих директив.

Директива #include. Включение файлов

Ранее уже использовалась директива #include. Эта директива подключает в исходный текст файлы. Она имеет следующие формы применения:

#include <имя_файла> 	// имя файла в угловых скобках
#include "имя_файла"		// имя файла в кавычках

Например, если нам надо задействовать в приложении консольный ввод-вывод с помощью функций printf() или scanf(), то нам надо подключить файл "stdio.h", который содержит определение этих функций:

#include <stdio.h>

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

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

  • assert.h: отвечает за диагностику программ

  • complex.h: для работы с комплексными числами

  • ctype.h: отвечает за преобразование и проверку символов

  • errno.h: отвечает за проверку ошибок

  • fenv.h: для доступа к окружению, которое управляет операциями с числами с плавающей точкой

  • float.h: отвечает за работу с числами с плавающей точкой

  • inttypes.h: для работы с большими целыми числами

  • iso646.h: содержит ряд определений, которые расширяют ряд логических операций

  • limits.h: содержит предельные значения целочисленных типов

  • locale.h: отвечает за работу с локальной культурой

  • math.h: для работы с математическими выражениями

  • setjmp.h: определяет возможности нелокальных переходов

  • signal.h: для обработки исключительных ситуаций

  • stdalign.h: для выравнивания типов

  • stdarg.h: обеспечивает поддержку переменного числа параметров

  • stdatomic.h: для выполнения атомарных операций по разделяемым данным между потоками

  • stdbool.h: для работы с типом _Bool

  • stddef.h: содержит ряд вспомогательных определений

  • stdint.h: для работы с целыми числами

  • stdio.h: для работы со средствами ввода-вывода

  • stdlib.h: содержит определения и прототипы функций общего пользования

  • stdnoreturn.h: содержит макрос noreturn

  • string.h: для работы со строками

  • tgmath.h: подключает math.h и complex.h плюс добавляет дополнительные возможности по работе с математическими вычислениями

  • threads.h: для работы с потоками

  • time.h: для работы с датами и временем

  • uchar.h: для работы с символами в кодировке Unicode

  • wchar.h: для работы с символами

  • wctype.h: содержит дополнительные возможности для работы с символами

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

Определение заголовочных файлов

Кроме стандартных заголовочных файлов мы можем подключать и свои файлы. Например, в той же папке, где находиться главный файл программы, определим еще один файл, который назовем numbers.c.

Препроцессор в программе на языке C и подключение файлов с помощью директивы include

Определим в нем следующий код:

int number = 5;

Здесь просто определена одна переменная. Теперь подключим этот файл в главный файл программы, который, допустим, называется app.c:

#include <stdio.h>
#include "numbers.c"
 
int main(void)
{
    printf("Number: %d", number);   // Number: 5
    return 0;
}

При подключении своих файлов их имя указывается в кавычках. И несмотря на то, что в программе не определена переменная number, она будет браться из подключенного файла numbers.c. Но опять же отмечу, важно, что в данном случае файл numbers.c располагается в одной папке с главным файлов программы.

Определение заголовочных файлов

В то же время данный способ прекрасно работает в GCC. Но для разных сред программирования способ подключения файлов может отличаться. Например, в Visual Studio мы получим ошибку. И более правильный подход будет состоять в том, что определить объявление объекта (переменной/константы) или функции в дополнительном заголовочном файле, а определение объекта или функции поместить в стандартный файл с расширением .c.

Например, в нашем в файле numbers.c уже есть определение переменной number. Теперь в ту же папку добавим новый файл numbers.h - файл с тем же названием, но другим расширением.

Подключение заголовочных файлов с помощью директивы include в программе на языке C

И определим в numbers.h следующий код:

extern int number;

Ключевое слово extern указывает, что данный объект является внешним. И в этом случае мы могли бы его подключить в файл исходного кода:

#include <stdio.h>
#include "numbers.h"		// объявление или описание объекта

int main(void)
{
	printf("%d", number);
	return 0;
}

Далее чтобы скомпилировать эту программу, передадим компилятору GCC оба файла с исходным кодом - app.c и numbers.c:

gcc app.c numbers.c -o app.exe && app

Компилятору передаются файлы через пробел и компилируются в один исполняемый файл.

Примечание для Visual Studio

Если разработка ведется в Visual Studio, то не надо подключать файл с исходным кодом (numbers.c), однако чтобы Visual Studio видела функционал файла в процессе разработки, может потребоваться подключить заголовочный файл (numbers.h). При этом заголовочный файл numbers.h помещается в папку Headers Files.

include directive in Visual Studio
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850