Препроцессор языка С
Теоретическая часть
Препроцессор (англ. preprocessor ) – программа, выполняющая предварительную обработку входных данных для другой программы [19.1]. Препроцессор языка программирования С просматривает программу до компилятора и заменяет в программе определенные сочетания символов (символические аббревиатуры) на соответствующие директивы. Он отыскивает и подключает к программе необходимые файлы и может изменить условия компиляции [19.1]. Препроцессор имеет тот же смысл, что и буферный процессор.
Препроцессор языка С выполняет макроподстановку, условную компиляцию и включение именованных файлов. Строки, начинающиеся со знака # (перед которыми разрешены символы пустого пространства), задают препроцессору инструкции-директивы. Их синтаксис не зависит от остальной части языка; они могут фигурировать где угодно и оказывать влияние (независимо от области действия) вплоть до конца единицы трансляции. Границы строк принимаются во внимание: каждая строка анализируется отдельно (но есть возможность и сцеплять строки). Лексемами для препроцессора являются все лексемы языка и последовательность символов, задающие имена файлов. Кроме того, любой символ, не определенный каким-либо другим способом, также воспринимается как лексема [19.2]. Влияние символов пустого пространства, отличающихся от пробелов и горизонтальных табуляций, внутри строк препроцессора не определено.
В предыдущих лабораторных работах уже встречались строки с начальным символом #. Это #include и #define. Первая директива (инструкция) использовалась для подключения заголовочных файлов, в первую очередь из библиотеки языка С, а вторая – для подстановки символов или чисел в определенные места программного кода.
Имеются следующие директивы препроцессора:
Каждая директива препроцессора должна занимать отдельную строку. Например, строка
#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.