Опубликован: 14.12.2010 | Уровень: для всех | Доступ: платный
Лекция 20:

Препроцессор языка С

< Лекция 19 || Лекция 20: 123 || Лекция 21 >
Аннотация: В лекции рассматриваются практически важные свойства препроцессора языка С и примеры типовых препроцессорных директив и конструкций.

Теоретическая часть

Препроцессор (англ. preprocessor ) – программа, выполняющая предварительную обработку входных данных для другой программы [19.1]. Препроцессор языка программирования С просматривает программу до компилятора и заменяет в программе определенные сочетания символов (символические аббревиатуры) на соответствующие директивы. Он отыскивает и подключает к программе необходимые файлы и может изменить условия компиляции [19.1]. Препроцессор имеет тот же смысл, что и буферный процессор.

Препроцессор языка С выполняет макроподстановку, условную компиляцию и включение именованных файлов. Строки, начинающиеся со знака # (перед которыми разрешены символы пустого пространства), задают препроцессору инструкции-директивы. Их синтаксис не зависит от остальной части языка; они могут фигурировать где угодно и оказывать влияние (независимо от области действия) вплоть до конца единицы трансляции. Границы строк принимаются во внимание: каждая строка анализируется отдельно (но есть возможность и сцеплять строки). Лексемами для препроцессора являются все лексемы языка и последовательность символов, задающие имена файлов. Кроме того, любой символ, не определенный каким-либо другим способом, также воспринимается как лексема [19.2]. Влияние символов пустого пространства, отличающихся от пробелов и горизонтальных табуляций, внутри строк препроцессора не определено.

В предыдущих лабораторных работах уже встречались строки с начальным символом #. Это #include и #define. Первая директива (инструкция) использовалась для подключения заголовочных файлов, в первую очередь из библиотеки языка С, а вторая – для подстановки символов или чисел в определенные места программного кода.

Имеются следующие директивы препроцессора:

#define #endif #ifdef #line
#elif #error #ifndef #pragma
#else #if #include #undef

Каждая директива препроцессора должна занимать отдельную строку. Например, строка

#include <stdio.h>    #include <stdlib.h>

рассматривается как недопустимая [19.3].

Директива #define

Директива #define определяет идентификатор и последовательность символов, которая будет подставляться вместо идентификатора каждый раз, когда он встретится в исходном файле. Идентификатор называется именем макроса, а сам процесс замены – макрозаменой (макрорасширением, макрогенерацией, макроподстановкой) [19.3]. В общем виде директива #define выглядит следующим образом (должно быть задано буквами латинского алфавита):

#define имя_макроса  последовательность_символов

В определении, как правило, в конце последовательности символов не ставится точки с запятой. Между идентификатором и последовательностью символов последовательность_символов может быть любое количество пробелов, но признаком конца последовательности символов может быть только разделитель строк [19.3].

Имена макросов обычно задаются с помощью букв верхнего регистра.

У директивы #define имя макроса может определяться с формальными параметрами. Тогда каждый раз, когда в программе встречается имя макроса, то используемые в его определении формальные параметры заменяются теми аргументами, которые встретились в программе. Такого рода макросы называются макросами с формальными параметрами (макроопределениями с параметрами и макросами, напоминающими функции ) [19.3]. Ключевым элементом макроса с параметрами являются круглые скобки, внутри которых находятся собственно формальные параметры. Рассмотрим пример макроса с тремя параметрами для определения следующего условия: будет ли остаток от деления случайной функции на правую границу интервала больше, чем половина этого интервала.

Программный код решения примера

#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <time.h>

#define MAX(a,b,c) ((1+rand()%(b)) > ((a)+(b))/2 ) ? (c):(b)

int main (void) {
	int a, b, c;
	srand((unsigned) time(NULL));

	printf("\n Enter a, b, c: ");
	scanf_s("%d%d%d", &a, &b, &c);
	printf("\n MAX(a,b,c): %d\n", MAX(a,b,c));	

	printf("\n\n ... Press any key: ");
	_getch();
	return 0;
}

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

Использование вместо настоящих функций макросов с формальными параметрами (т. е. a, b, с ) дает следующее преимущество: увеличивается скорость выполнения кода, потому что в таких случаях не надо тратить ресурсы на вызов функций. Кроме того, макрос не чувствителен к типу данных, т. е. в нем отсутствует проверка типов аргументов. Однако если у макроса с формальными параметрами очень большие размеры, то тогда из-за дублирования кода увеличение скорости достигается за счет увеличения размеров программы [19.3]. И еще, в конструировании макроса следует быть очень внимательным. Как правило, макросы используются для небольших пользовательских функций [19.4].

Директива #error

Директива #error заставляет компилятор прекратить компиляцию [19.3]. Эта директива используется в основном для отладки. В общем виде директива #error выглядит следующим образом:

#error  сообщение – об – ошибке

Заданное сообщение об ошибке ( сообщение – об – ошибке ) в двойные кавычки не заключается. Когда встречается данная директива, то выводится сообщение об ошибке – возможно, вместе с другой информацией, определяемой компилятором [19.3].

Директива #include

Директива #include дает указание компилятору читать еще один исходный файл – в дополнение к тому файлу, в котором находится сама эта директива [19.3]. Имя исходного файла (подключаемого файла) должно быть заключено в двойные кавычки или в угловые скобки. Обычно имена стандартных заголовочных файлов заключают в угловые скобки. А использование кавычек обычно приберегается для имен специальных файлов, относящихся к конкретной программе. Способ поиска файла зависит от того, заключено ли его имя в двойные кавычки или же в угловые скобки. Если имя заключено в угловые скобки, то поиск файла проводится тем способом, который определен в компиляторе. Часто это означает поиск определенного каталога, специально предназначенного для хранения таких файлов. Если имя заключено в кавычки, то поиск файла проводится другим способом. Во многих компиляторах это означает поиск файла в текущем рабочем каталоге. Если же файл не найден, то поиск повторяется уже так, как будто имя файла заключено в угловые скобки [19.3].

Файлы, имена которых находятся в директивах #include, могут в свою очередь содержать другие директивы #include. Они называются вложенными директивами #include. Количество допустимых уровней вложенности у разных компиляторов может быть разным. Однако в стандарте С89 предусмотрено, что компиляторы должны допускать не менее 8 таких уровней [19.3].

Директивы условной компиляции

Директивы условной компиляции (будут рассмотрены ниже) дают возможность выборочно компилировать части исходного кода программы. Этот процесс называется условной компиляцией [19.3].

Директива условной компиляции #if выглядит следующим образом:

#if  константное_выражение
	последовательность операторов программного кода
#endif

Если находящееся за директивой #if константное выражение истинно, то компилируется код, который находится между этим выражением и #endif, которая обозначает конец блока #if. Константное выражение может быть задано через директиву #define. При этом, если, например, задано число, не равное нулю, то такое константное выражение будет истинно. Если же заданное число есть нуль, то константное выражение будет ложным. В частности константное выражение может быть задано макросом с формальными параметрами, которые должны быть в свою очередь также константными параметрами.

Директива условной компиляции #else используется практически также как в обычном условном операторе языка С: if – else. То есть логика действия позволяет перенаправить выполнение программы. Дополнительная директива условной компиляции #else в общем случае имеет вид

#if  константное_выражение
	последовательность операторов программного кода
#else
альтернативная последовательность операторов программного кода
#endif

Аналогично используются директивы #elif (else if), которые в общем случае имеют следующий вид:

#if  константное_выражение
	последовательность операторов программного кода
#elif  2_ константное_выражение
2_ я_последовательность операторов программного кода
#elif  3_ константное_выражение
3_ я_последовательность операторов программного кода
.
.
.
#elif  N_ константное_выражение
N_ я_последовательность операторов программного кода
#else
альтернативная последовательность операторов программного кода
#endif

Если константное выражение в директиве #elif истинно (не нулевое, например), то будет компилироваться соответствующая последовательность операторов программного кода. При этом другие выражения в директивах #elif проверяться уже не будут, в том числе и директива #else.

Особенностью рассмотренных конструкций является то, что проверка выражений осуществляется внутри директив #if и #endif.

В соответствии со стандартом С89 у директив #if и #elif может быть не менее 8 уровней вложенности. При вложенности каждая директива #endif, #else или #elif относится к ближайшей директиве #if или #elif [19.3].

Каждая директива #if сопровождается директивой #endif.

Директива условной компиляции #ifdef в общем виде выглядит следующим образом:

#ifdef  имя_макроса
последовательность операторов
#endif

Директива условной компиляции #ifdef означает "if defined" (если определено) [19.3]. Последовательность операторов будет компилироваться, если имя макроса было определено ранее с помощью директивы #define.

Директива условной компиляции #ifndef означает "if not defined" (если не определено) в общем виде выглядит следующим образом:

#ifndef  имя_макроса
последовательность операторов
#endif

Последовательность операторов будет компилироваться, если имя макроса еще не определено директивой #define. В директивах #ifdef и #ifndef можно использовать #else или #elif.

Согласно стандарту С89 допускается не менее 8 уровней #ifdef и #ifndef.

Директива #undef удаляет заданное определение имени макроса, то есть "аннулирует" его определение; само имя макроса должно находиться после директивы [19.3].

В общем случае директива #undef выглядит следующим образом:

#undef  имя_макроса

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

Для того чтобы узнать определено ли имя макроса, можно использовать директиву #if в сочетании с оператором времени компиляции defined [19.3].

Оператор defined выглядит следующим образом:

defined  имя_макроса

Если имя_макроса определено, то выражение считается истинным; в противном случае – ложным.

Единственная причина, по которой используется оператор defined, состоит в том, что с его помощью в #elif можно узнать, определено ли имя макроса [19.3].

Директива #line

Директива #line изменяет содержимое __LINE__ и __FILE__, которые являются зарезервированными идентификаторами (макросами) в компиляторе. В первом из них содержится номер компилируемой в данный момент строки программного кода программы [19.3]. А второй идентификатор – это строка, содержащая имя компилируемого исходного файла.

Директива #line выглядит следующим образом:

#line  номер  "имя_файла"

В определении директивы #line обязательным является номер строки, относительно которой будет выполняться подсчет следующих строк. Второй параметр "имя_файла" является не обязательным. Если его не будет, то идентификатор __FILE__ будет содержать путь и имя программы. Если указать в качестве параметра новое имя файла – "имя_файла", то __FILE__ будет содержать это новое имя файла.

Директива #line в основном используется для отладки и специальных применений [19.3].

Операторы препроцессора # и ##

Операторы # и ## применяются в сочетании с директивой #define [19.3]. Оператор #, который обычно называют оператором превращения в строку (stringize), превращает аргумент, перед которым стоит, в строку, заключенную в кавычки. Оператор # должен использоваться в макросах с аргументами, поскольку операнд после # ссылается на аргумент макроса [19.5].

Оператор ##, который называют оператором склеивания (pasting), или конкатенации конкатенирует две лексемы. Операция ## должна иметь два операнда [19.5].

Операторы # и ## предусмотрены для работы препроцессора в некоторых особых случаях [19.3,19.5].

Директива #pragma

Директива #pragma – это определяемая реализацией директива, которая позволяет передавать компилятору различные инструкции [19.3]. Она позволяет помещать инструкции компилятору в исходный код [19.4]. Возможности этой директивы следует изучать по документации по компилятору.

Предопределенные символические константы

В языке С определены пять встроенных, предопределенных имен макрокоманд [19.2-19.3-19.4-19.5], которые представлены в табл. 19.1.

Таблица 19.1.
Предопределенные символические константы
Символическая константа Объяснение
__LINE__ Возвращает целую константу для номера текущей обрабатываемой строки исходного кода программы
__FILE__ По умолчанию возвращает в виде строки символов имя компилируемого исходного файла
__DATE__ Возвращает в виде строки символов дату (мм дд гг) начала компиляции текущего исходного файла
__TIME__ Возвращает в виде строки символов время (чч:мм:сс) начала компиляции текущего исходного файла
__STDC__ Возвращает целую константу 1, которая указывает на то, что данная реализация совместима со стандартом ANSI
< Лекция 19 || Лекция 20: 123 || Лекция 21 >
Мухаммадюсуф Курбонов
Мухаммадюсуф Курбонов
Александр Соболев
Александр Соболев
Россия
Артем Полутин
Артем Полутин
Россия, Саранск