Сайт о телевидении

Сайт о телевидении

» » Для определения макроса в си используется директива. Препроцессор языка си

Для определения макроса в си используется директива. Препроцессор языка си

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

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

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

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

    #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. Данный файл еще называется заголовочным. Заголовочные файлы содержат прототипы функций, определения и описания типов и констант и имеют расширение .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 : содержит дополнительные возможности для работы с символами

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

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

Int number = 5;

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

#include #include "main.c" int main(void) { printf("%d", number); // 5 return 0; }

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

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

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

Extern int number;

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

#include #include "main.h" // объявление или описание объекта #include "main.c" // определение объекта int main(void) { printf("%d", number); return 0; }

Этот пример также будет работать в GCC, однако как уже было рассмотрено выше, подключение файла main.h для GCC необязательно.

Если разработка ведется в Visual Studio , то определение объекта не надо подключать исходный файл:

#include #include "main.h" // объявление или описание объекта int main(void) { printf("%d", number); return 0; }

Несмотря на то, что здесь явным образом не подключается файл main.c, но при трансляции Visual Studio через заголовочный файл main.h сможет подключить находящийся в той же папке файл main.c.

Национальный Открытый Университет "ИНТУИТ": www.intuit.ru Нина Калинина, Нина Костюкова Лекция 11. Препроцессор языка Си

Общие сведения

В интегрированную среду подготовки программ на Си или вкомпилятор языка как обязательныйкомпонент входитпрепроцессор . Назначение препроцессора - обработка исходного текста программы до ее компиляции. Препроцессорная обработка включает несколько стадий, выполняемых последовательно. Конкретная реализация может объединять несколько стадий, но результат должен быть таким, как если бы они выполнялись в следующем порядке:

1. Все системно-зависимые обозначения перекодируются в стандартные коды.

2. Каждая пара из символов " \ " и "конец строки" вместе с пробелами между ними убираются, и тем самым следующая строка исходного текста присоединяется к строке, в которой находилась эта пара символов.

3. В тексте распознаются директивы и лексемы препроцессора, а каждый комментарий заменяется одним символом пустого промежутка.

4. Выполняются директивы препроцессора и производятся макроподстановки.

5. Эскейп-последовательности в символьных константах и символьных строках заменяются на их эквиваленты.

6. Смежные символьные строки конкатенируются, то есть соединяются в одну строку.

7. Каждая препроцессорная лексема преобразуется в текст на языке Си.

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

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

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

Символические константы: #define

Если в качестве первого символа в строке программы используется символ # , то эта строка являетсякомандной строкой препроцессора (макропроцессора).Командная строка препроцессора заканчивается символом перевода на новую строку. Если непосредственно перед концом строки поставить символ обратной косой черты "\ ", токомандная строка будет продолжена на следующую строку программы.

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

Замена идентификаторов

#define идентификатор строка

Заменяет каждое вхождение идентификатора ABC в тексте программы на100 :

#undef идентификатор

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

Отменяет предыдущее определение для идентификатора ABC .

/* Простые примеры директивы препроцессора */ #define TWO 2 /* можно использовать комментарии*/ #define MSG "Текст 1.\

Продолжение текста 1"

/* обратная косая черта продолжает определение на следующую строку */ #define FOUR TWO*TWO

#define PX printf("X равен %d.\n", x) #define FMT "X равен %d.\n"

int x = TWO; PX;

x = FOUR; printf(FMT,x); printf("%s\n",MSG); printf("TWO:MSG\n"); return TWO;

В результате выполнения нашего примера будем иметь:

X равен 2 X равен 4

Текст 1. Продолжение текста 1 TWO: MSG

Разберем, что произошло. Оператор

превращается в

Затем оператор

превращается в

printf("X равно %d.\n",x);

поскольку сделана полная замена. Теперь мы видим, что макроопределение может представлять любую строку, даже целое выражение на языке Си. Заметим, что это константная строка. PX напечатает только переменную, названнуюx .

В следующей строке выполняется следующее: x = FOUR;

превращается

www.intuit.ru/studies/professional_skill_improvements/1747/courses/43/print_lecture/663

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

превращается в

и на этом все заканчивается. Фактическое умножение имеет место не во время работы препроцессора и не при компиляции, а всегда без исключения при работе программы. Препроцессор не выполняет вычислений . Он только очень точно делает предложенные подстановки. Заметим, что макроопределение может включать другие определения. Некоторые компиляторы не поддерживают это свойство вложения. В следующей строке

превращается в

printf("X равно %d.\n",x)

когда FMT заменяется соответствующей строкой. Этот подход может оказаться очень удобным, если есть длинная строка, которую мы используем несколько раз. В следующей строке программы MSG заменяется соответствующей строкой. Кавычки делают замещающую строку константой символьной строки. Поскольку программа получает ее содержимое, эта строка будет запоминаться в массиве, заканчивающемся нуль-символом. Так,

#define HAL "X" определяет символьную константу, а

#define HAR "X" определяет символьную строку X\0

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

printf("TWO: MSG");

печатает буквально TWO: MSG вместо печати следующего текста:

2: "Текст 1.

Продолжение текста 1"

Если нам нужно напечатать этот текст, можно использовать оператор

printf("%d: %s\n",TWO,MSG);

потому что здесь макроопределения находятся вне кавычек.

Когда следует использовать символические константы ? Вероятно, мы должны применять их для большинства чисел. Если число является константой, используемой в вычислениях, тосимволическое имя делает яснее ее смысл. Если число - размер массива, тосимволическое имя упрощает изменение вашей программы при работе с большим массивом. Если число является системным кодом, скажем для символаEOF , то символическое представление делает программу более переносимой. Изменяется только определениеEOF . Мнемоническое значение, легкость изменения, переносимость: все это делает

символические константы заслуживающими внимания!

Использование аргументов с #define

Во избежание ошибок при вычислении выражений параметры макроопределения необходимо заключать

в скобки.

#define идентификатор1 (идентификатор2, . . .) строка

#define abs(A) (((A) > 0)?(A) : -(A))

www.intuit.ru/studies/professional_skill_improvements/1747/courses/43/print_lecture/663

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

Каждое вхождение выражения abs(arg) в тексте программы заменяется на

((arg) > 0) ? (arg) : -(arg),

причем параметр макроопределенияА заменяется на arg .

#define nmem(P,N)\

(P) -> p_mem[N].u_long

Символ \ продолжает макроопределение на вторую строчку. Это макроопределение уменьшает сложность выражения, описывающегомассив объединений внутри структуры.

Макроопределение с аргументами очень похоже на функцию, поскольку аргументы его заключены в скобки:

/* макроопределение с аргументами */ #define SQUARE(x) x*x

#define PR(x) printf("x равно %d.\n", x)

PR(SQUARE(x+2));

PR(100/SQUARE(2));

PR(SQUARE(++x)); return 0;

Всюду, где в нашей программе появляется макроопределение SQUARE(x) , оно заменяется наx*x. В отличие от наших прежних примеров, при использовании этогомакроопределения мы можем совершенно свободно применять символы, отличные отx . В макроопределении "x " замещается символом, использованным в макровызове программы. Поэтому макроопределениеSQUARE(2) замещается на2*2 . Таким образом,x действует какаргумент . Однако,аргумент макроопределения не работает - точно так же, какаргумент функции. Вот результаты выполнения программы:

z равно 16. z равно 4.

SQUARE(x) равно 16. SQUARE(x+2) равно 14. 100/SQUARE(2) равно 100. SQUARE(++x) равно 30.

Первые две строки очевидны. Заметим, что даже внутри двойных кавычек в определении PR переменная замещается соответствующим аргументом. Все аргументы в этом определении замещаются. Рассмотрим третью строку:

Она становится следующей строкой:

printf("SQUARE(x) равно %d.\n", SQUARE(x));

www.intuit.ru/studies/professional_skill_improvements/1747/courses/43/print_lecture/663

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

после первого этапа макрорасширения. Второе SQUARE(x) расширяется, превращаясь вx*x , а первое остается без изменения, потому что теперь оно находится внутри кавычек в операторе программы, и таким образом защищено от дальнейшего расширения. Окончательно строка программы содержит

printf("SQUARE(x) равно %d.\n",x*x);

и выводит на печать

SQUARE(x) равно x*x.

Если макроопределение включает аргумент с двойными кавычками, тоаргумент будет замещаться строкой из макровызова. Но после этого он в дальнейшем не расширяется, даже если строка является еще одним макроопределением. В нашем примерепеременная x стала макроопределениемSQUARE(x) и осталась им. Вспомним, чтоx=4 . Это позволяет предположить, чтоSQUARE(x+2) будет равно6*6 или36 . Но напечатанный результат говорит, что получается число14 . Причина такого результата такова:препроцессор не делает вычислений. Он только замещает строку. Всюду, где нашеопределение указывает наx ,препроцессор подставит строкуx+2 .

Таким образом,

x*x становится x+2*x+2

Если x равно4 , то получается

4+2*4+2=4+8+2=14

Вызов функции передаетзначение аргумента в функцию вовремя выполнения программы. Макровызов передает строку аргументов в программу до ее компиляции.

Макроопределение или функция?

! Многие задачи можно решать, используя макроопределение с аргументами или функцию. Что из них следует применять? На этот счет нет строгих правил, но есть некоторые соображения.

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

Выбор макроопределения приводит к увеличению объема памяти, а выбор функции - к увеличению времени работы программы. Макроопределение создает строчный код, т.е. мы получаем оператор в программе. Если макроопределение применить 20 раз, то в программу вставится 20 строк кода. Если мы используем функцию 20 раз, то у нас будет только одна копия операторов функции. Однако управление программой следует передать туда, где находитсяфункция , а затем вернуться в вызывающую программу, а на это потребуется больше времени, чем при работе со строчными кодами. Так что думайте, что выбирать!

Преимущество макроопределений заключается в том, что при их использовании нам не нужно беспокоиться о типах переменных, т.к. макроопределения имеют дело с символьными строками, а не с фактическими значениями. Tак наше макроопределениеSQUARE(x) можно использовать одинаково хорошо с переменными типаint илиfloat .

Запомним!

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

2. Используйте круглые скобки для каждого аргумента и всего определения. Это является гарантией того, что элементы будут сгруппированы надлежащим образом в выражении.

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

www.intuit.ru/studies/professional_skill_improvements/1747/courses/43/print_lecture/663

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

макроопределений.

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

Включение файла: #include

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

assert.h - диагностикапрограмм

ctype.h - преобразование и проверка символов

errno.h - проверка ошибок

float.h - работа с вещественными данными

limits.h - предельные значения целочисленных данных

locale.h -поддержка национальной среды

math.h - математические вычисления

setjump.h - возможности нелокальных переходов

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

stdarg.h -поддержка переменного числа параметровstddef.h - дополнительные определения

stdio.h - средства ввода-вывода

stdlib.h - функции общего назначения (работа с памятью)string.h - работа со строками символов

time.h -определение дат и времени

В конкретных реализациях количество и наименование заголовочных файлов могут быть и другими. Например, в компиляторах для MS-DOS активно используются заголовочные файлыmem.h ,alloc.h ,conio.h ,dos.h и другие. В компиляторах Turbo C, Borland C++ для связи с графической библиотекой применяетсязаголовочный файл graphics.h .

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

#include <имя_файла>

#include

Процессор заменяет эту строку содержимым файлаmath.h . Угловые скобки означают, чтофайл math.h будет взят из некоторого стандартного каталога (обычно это/usr/include ).Текущий каталог не просматривается:

#include "имя_файла"

Препроцессор заменяет эту строку содержимым файлаABC . Так какимя файла заключено в кавычки, топоиск производится в текущем каталоге (в котором содержится основнойфайл исходного текста). Если в текущем каталоге данного файла нет, топоиск производится в каталогах, определенных именем пути в опции -l препроцессора. Если и там нет файла, то просматривается стандартный каталог.

www.intuit.ru/studies/professional_skill_improvements/1747/courses/43/print_lecture/663

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

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

В конкретных реализациях количество и наименования заголовочных файлов могут быть разными:

#include "/user/1/my.h" ищет в каталоге /user/1

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

#include "stdio.h" ищет на стандартном диске

#include ищет на стандартном диске

#include "a:stdio.h" ищет на диске а

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

Некоторые файлы включены в систему, например, stdio.h , но можно создать и свойфайл .

Многие программисты разрабатывают свои стандартные заголовочные файлы, чтобы использовать их в программах.

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

Командные строки препроцессора используются для условной компиляции различных частей исходного текста в зависимости от внешних условий.

#if константное_выражение

Истина , если константноевыражение ABC + 3 не равно нулю.

#ifdef идентификатор

истина , еслиидентификатор ABC определен ранее командой#define .

#ifndef идентификатор

истина , еслиидентификатор ABC не определен в настоящий момент.

Если предшествующие проверки #if ,#ifdef или#ifndef даютзначение "Истина ", то строки от#else до#endif игнорируются при компиляции.

Команда #endif обозначает конец условной компиляции.

fprintf (stderr, "location: x = %d\n", x); #endif

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

Номер строки и имя файла

#line целая_константа "имя_файла"

Препроцессор изменяет номер текущей строки и имя компилируемого файла. Имя файла может быть опущено.

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

#define N 3/*определение константы */

#line 55 "file.c"

Реакция на ошибки

#error последовательность лексем

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

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

#error NAME должно быть равно 15!

Сообщение будет выглядеть так:

error <имя_файла><номер_строки >;

error directive: NAME должно быть равно 15!

Пустая директива

www.intuit.ru/studies/professional_skill_improvements/1747/courses/43/print_lecture/663

FILE__

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

Использование этой директивы не вызывает никаких действий.

Эта директива определяет действия, зависящие от конкретной реализации компилятора. Например в некоторые компиляторы входит вариант этой директивы для извещения компилятора о наличии в тексте программы команд на языке Ассемблер. Возможности команды #pragma могут быть разнообазными. Стандарта для них не существует. Если конкретный препроцессор встречает прагму, которая ему неизвестна, он ее просто игнорирует как пустую директиву. В некоторых реализациях включена прагма.

#pragma pack(n) , гдеn= 1 ,2 ,4 . Прагмаpack позволяет влиять на упаковку смежных элементов в структурах и объединениях (см. лекцию 14).

Соглашение может быть таким:

pack(1) - выравнивание элементов по границам байтов;

pack(2) - выравнивание элементов по границам слов;

pack(4) - выравнивание элементов по границам двойных слов;

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

Встроенные макроимена

Существуют встроенные (заранее определенные) макроимена, доступные препроцессору во время обработки. Они позволяют получить следующую информацию:

DATE__ -строка символов в формате: "месяц число год", определяющая дату начала обработки исходного файла. Например, после препроцессорной обработки текста программы, выполненной 29 января 2005 года, оператор

printf(__DATE__);

станет таким

printf("%s", "January 29 2005");

LINE__ - десятичная константа - номер текущей обрабатываемой строки файла с программой наСи . Принято, что номер первой строки исходного файла равен1 ;

FILE__ -строка символов - имя компилируемого файла. Имя изменяется всякий раз, когдапрепроцессор встречает директиву#include с указанием имени другого файла. Когда включения файлапо команде#include завершаются, востанавливается предыдущеезначение макроимени

TIME__ -строка символов вида "часы:минуты:секунды", определяющая время начала обработки препроцессором исходного файла;

STDC__ - константа, равная1 , есликомпилятор работает в соответствии с ANSI-стандартом. В противном случаезначение микроимени__STDC__ не определено.Стандарт языка Си предполагает, что наличие имени__STDC__ определяется реализацией, так какмакрос __STDC__ относится к нововведениям стандарта. В конкретных реализациях набор предопределенных имен гораздо шире. Для получения более полных сведений о предопределенных препроцессорных именах следует обращаться к документациипо конкретному компилятору.

Внимание! Если Вы увидите ошибку на нашем сайте, выделите её и нажмите Ctrl+Enter.

© Национальный Открытый Университет "ИНТУИТ", 2012 | www.intuit.ru

www.intuit.ru/studies/professional_skill_improvements/1747/courses/43/print_lecture/663

Почти все программы на языке С++ используют специальные команды для компилятора, которые называются директивами. В общем случае директива – это указание компилятору языка С++ выполнить то или иное действие в момент компиляции программы. Существует строго определенный набор возможных директив, который включает в себя следующие определения:

#define, #elif, #else, #endif, #if, #ifdef, #ifndef, #include, #undef.

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

Следует заметить, что символ ‘;’ после директив не ставится. Приведем примеры вариантов использования директивы #define.

Листинг 1.2. Примеры использования директивы #define.

#include
#define TWO 2
#define FOUR TWO*TWO
#define PX printf(“X равно %d.\n”, x)
#define FMT «X равно %d.\n»
#define SQUARE(X) X*X
int main()
{
int x = TWO;
PX;
x = FOUR;
printf(FMT, x);
x = SQUARE(3);
PX;

Return 0;
}

После выполнения этой программы на экране монитора появится три строки:

X равно 2.
X равно 4.
X равно 9.

Директива #undef отменяет определение, введенное ранее директивой #define. Предположим, что на каком-либо участке программы нужно отменить определение константы FOUR. Это достигается следующей командой:

Интересной особенностью данной директивы является возможность переопределения значения ранее введенной константы. Действительно, повторное использование директивы #define для ранее введенной константы FOUR невозможно, т.к. это приведет к сообщению об ошибке в момент компиляции программы. Но если отменить определение константы FOUR с помощью директивы #undef, то появляется возможность повторного использования директивы #define для константы FOUR.

Для того чтобы иметь возможность выполнять условную компиляцию, используется группа директив #if, #ifdef, #ifndef, #elif, #else и #endif. Приведенная ниже программа выполняет подключение библиотек в зависимости от установленных констант.

#if defined(GRAPH)
#elif defined(TEXT)
#else
#endif

Данная программа работает следующим образом. Если ранее была задана константа с именем GRAPH через директиву #define, то будет подключена графическая библиотека с помощью директивы #include. Если идентификатор GRAPH не определен, но имеется определение TEXT, то будет использоваться библиотека текстового ввода/вывода. Иначе, при отсутствии каких-либо определений, подключается библиотека ввода/вывода. Вместо словосочетания #if defined часто используют сокращенные обозначения #ifdef и #ifndef и выше приведенную программу можно переписать в виде:

#ifdef GRAPH
#include //подключение графической библиотеки
#ifdef TEXT
#include //подключение текстовой библиотеки
#else
#include //подключение библиотеки ввода-вывода
#endif

Отличие директивы #if от директив #ifdef и #ifndef заключается в возможности проверки более разнообразных условий, а не только существует или нет какие-либо константы. Например, с помощью директивы #if можно проводить такую проверку:

#if SIZE == 1
#include // подключение математической библиотеки
#elif SIZE > 1
#include // подключение библиотеки обработки массивов
#endif

В приведенном примере подключается либо математическая библиотека, либо библиотека обработки массивов, в зависимости от значения константы SIZE.

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

Листинг 1.3. Пример компиляции отдельных блоков программы.

#include
#define SQUARE
int main()
{
int s = 0;
int length = 10;
int width = 5;

#ifdef SQUARE
s=length*width;
#else
s=2*(length+width);
#endif

Return 0;
}

В данном примере происходит вычисление либо площади прямоугольника, либо его периметра, в зависимости от того определено или нет значение SQUARE. По умолчанию программа вычисляет площадь прямоугольника, но если убрать строку #define SQUARE, то программа станет вычислять его периметр.

Используемая в приведенных примерах директива #include позволяет добавлять в программу ранее написанные программы и сохраненные в виде файлов. Например, строка

#include < stdio.h >

указывает препроцессору добавить содержимое файла stdio.h вместо приведенной строки. Это дает большую гибкость, легкость программирования и наглядность создаваемого текста программы. Есть две разновидности директивы #include:

#include < stdio.h > - имя файла в угловых скобках

#include «mylib.h» - имя файла в кавычках

Угловые скобки сообщают препроцессору о том, что необходимо искать файл (в данном случае stdio.h) в одном или нескольких стандартных системных каталогах. Кавычки свидетельствуют о том, что препроцессору необходимо сначала выполнить поиск файла в текущем каталоге, т.е. в том, где находится файл создаваемой программы, а уже затем – искать в стандартных каталогах.

Введение

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

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

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

Символ # должен быть первым в строке, содержащей директиву в СП MSC версии 4. В СП MSC версии 5 ив СП ТС ему могут предшествовать пробельные символы. Как в СП MSC, так и в СП ТС пробельные символы допускаются между символом # и первой буквой директивы.

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

Указания компилятору, или прагмы, представляют собой инструкции, записываемые в исходном тексте программы и предназначенные для управления действиями компилятора языка Си в определенных ситуациях. Набор указаний компилятору и их смысл различаются для разных компиляторов языка Си, поэтому в разделе 7.8 описывается только общий синтаксис указаний компилятору.

В рассматриваемых системах программирования есть возможность получить промежуточный текст программы после работы препроцессора, до начала собственно компиляции. В этом файле уже выполнены макроподстановки, а все строки, содержащие директивы #define и #undef, заменены на пустые строки. На место строк #include подставлено содержимое соответствующих включаемых файлов. Выполнена обработка директив условной компиляции #if, #elif, #else, #ifdef, #ifndef, #endif, а строки, содержащие их, заменены пустыми строками. Пустыми строками заменены и исключенные в процессе условной компиляции фрагменты исходного текста. Кроме того, в этом файле есть строки следующего вида:

#["имя файла"]

которые соответствуют точкам изменения номера текущей строки и/или номера файла по директивам #line или #include.

Именованные константы и макроопределения

Директива #define обычно используется для замены часто используемых в программе констант, ключевых слов, операторов и выражений осмысленными идентификаторами. Идентификаторы, которые заменяют числовые или текстовые константы либо произвольную последовательность символов, называются именованными константами. Идентификаторы, которые представляют некоторую последовательность действий, заданную операторами или выражениями языка Си, называются макроопределениями. Макроопределения могут иметь аргументы. Обращение к макроопределению в программе называется макровызовом.

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

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

Макроопределение напоминает по синтаксису определение функции. Однако замена вызова функции макровызовом может повысить скорость выполнения программы, поскольку для макроопределения не требуется генерировать вызывающую последовательность, которая занимает относительно большое время (засылка аргументов в стек, передача управления и т.п.). С другой стороны, многократное употребление макроопределения в программе может потребовать значительно большей памяти, чем вызовы функции (код для функции генерируется один раз, а для макроопределения - столько раз, сколько имеется макровызовов в программе).

Имеется возможность задавать определения именованных констант не только в исходном тексте, но и в командной строке компиляции.

Имеется ряд предопределенных идентификаторов, которые нельзя использовать в директивах #define и #undef в качестве идентификаторов. Они рассмотрены в разделе 7.9 "Псевдопеременные".

Директива #define

Синтаксис:

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

представляет собой набор лексем, таких как ключевые слова, константы, идентификаторы или выражения. Один или более пробельных символов должны отделять от (или от заключенных в скобки параметров). Если текст не умещается на строке, то он может быть продолжен на следующей строке; для этого следует набрать в конце строки символ обратный слэш и сразу за ним нажать клавишу ENTER.

может быть опущен. В этом случае все экземпляры будут удалены из исходного текста программы. Тем не менее, сам рассматривается как определенный и при проверке директивой #if дает значение 1 (смотри раздел 7.4.1).

Если он задан, содержит один или более идентификаторов, разделенных запятыми. Идентификаторы в списке должны отличаться друг от друга. Их область действия ограничена макроопределением, в котором они заданы. Список должен быть заключен в круглые скобки. Имена формальных параметров в отмечают позиции, в которые должны быть подставлены фактические аргументы макровызова. Каждое имя формального параметра может появиться в произвольное число раз.

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

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

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

Внутрь в директиве #define могут быть вложены имена других макроопределений или констант. Их расширение производится лишь при расширении этого, а не при его определении директивой #define. Это надо учитывать, в частности, при взаимодействии вложенных именованных констант и макроопределений с директивой #undef: к моменту расширения содержащего их текста они могут уже оказаться отменены директивой #undef.

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

не приведет к зацикливанию препроцессора.

/* пример 1 */

#define WIDTH 80

#define LENGTH (WIDTH + 10)

/* пример 2 */

#define FILEMESSAGE "Попытка создать файл\

не удалась из-за нехватки дискового пространства"

/* пример 3 */

#define REG1 register

#define REG2 register

/* пример 4 */

#define MAX(x, y)((x)>(у)) ? (x) : (у)

/* пример 5 */

#define MULT(a, b) ((a)*(b))

В первом примере идентификатор WIDTH определяется как целая константа со значением 80, а идентификатор LENGTH - как текст (WIDTH + 10). Каждое вхождение идентификатора LENGTH в исходный файл будет заменено на текст (WIDTH + 10), который после расширения идентификатора WIDTH превратится в выражение (80 + 10). Скобки, окружающие текст (WIDTH + 10), позволяют избежать ошибок в операторах, подобных следующему:

var = LENGTH * 20;

После обработки препроцессором оператор примет вид:

var = (80 + 10)* 20;

Значение, которое присваивается var, равно 1800. В отсутствие скобок в макроопределении оператор имел бы следующий вид:

var = 80 + 10*20;

Значение var равнялось бы 280, поскольку операция умножения имеет более высокий приоритет, чем операция сложения.

Во втором примере определяется идентификатор FILEMESSAGE. Его определение продолжается на вторую строку путем использования символа обратный слэш непосредственно перед нажатием клавиши ENTER.

В третьем примере определены три идентификатора, REG1, REG2, REG3. Идентификаторы REG1 и REG2 определены как ключевые слова register. Определение REG3 опущено и, таким образом, любое вхождение REG3 будет удалено из исходного файла. В разделе 7.4.1 приведен пример, показывающий, как эти директивы могут быть использованы для задания класса памяти register наиболее важным переменным программы.

В четвертом примере определяется макроопределение МАХ. Каждое вхождение идентификатора МАХ в исходном файле заменяется на выражение ((x)>(у))?(x):(у), в котором вместо формальных параметров х и у подставлены фактические. Например, макровызов

заменится на выражение

((1)>(2))?(1):(2)

а макровызов

заменится на выражение

((i)>(s(i]))?(i):(s(i])

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

заменится на выражение

((i)>(s))?(i):(s)

Операнды операции > могут быть вычислены в любом порядке, а значение переменной i зависит от порядка вычисления. Поэтому результат выражения непредсказуем. Кроме того, возможна ситуация, когда переменная i будет инкрементирована дважды, что, вероятно, не требуется.

В пятом примере определяется макроопределение MULT. Макровызов MULT(3,5) в тексте программы заменяется на (3)*(5). Круглые скобки, в которые заключаются фактические аргументы, необходимы в тех случаях, когда аргументы макроопределения являются сложными выражениями. Например, макровызов

заменится на (3+4)*(5+6), что равняется 76. В отсутствие скобок результат подстановки 3+4*5+6 был бы равен 29.

Склейка лексем и преобразование аргументов макроопределений

СП ТС и версия 5.0 СП MSC реализуют две специальные препроцессорные операции: ## и #.

В директиве #define две лексемы могут быть "склеены" вместе. Для этого их нужно разделить знаками ## (слева и справа от ## допустимы пробельные символы). Препроцессор объединяет такие лексемы в одну; например, макроопределение

#define VAR (i, j) i##j

при макровызове VAR(х,6) образует идентификатор х6. Некоторые компиляторы позволяют в аналогичных целях употребить запись х/**/6, но этот метод менее переносим.

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

Пример: макроопределение TRACE позволяет печатать с помощью стандартной функции printf значения переменных типа int в формате = .

#define TRACE(flag) printf (#flag " = %d\n", flag)

Следующий фрагмент текста программы:

TRACE (highval);

примет после обработки препроцессором вид:

printf("highval" " = %d\n", highval);

Следующие друг за другом символьные строки рассматриваются компилятором языка Си в СП MSC версии 5 и в СП ТС как одна строка, поэтому полученная запись эквивалентна следующей:

printf("highval = %d\n", highval);

При макровызове сначала выполняется макроподстановка всех аргументов макровызова, а затем их подстановка в тело макроопределения. Поэтому следующая программа напечатает строку "отклонение от стандарта":

#define АВ "стандарт"

#define А "отклонение"

#define В "от стандарта"

#define CONCAT(P,Q) Р##Q

printf(CONCAT(A,В) "\n");

Директива #undef

Синтаксис:

Директива #undef отменяет действие текущего определения #define для. Чтобы отменить макроопределение посредством директивы #undef, достаточно задать его. Задание списка параметров не требуется.

Не является ошибкой применение директивы #undef к идентификатору, который ранее не был определен (или действие его определения уже отменено). Это может использоваться для гарантии того, что идентификатор не определен.

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

#define WIDTH 80

#define ADD(X, Y) (X)+(Y)

В этом примере директива #undef отменяет определение именованной константы WIDTH и макроопределения ADD. Обратите внимание на то, что для отмены макроопределения задается только его идентификатор.

Включение файлов

Синтаксис:

#include "имя пути"

Директива #include включает содержимое исходного файла, которого задано, в текущий компилируемый исходный файл. Например, общие для нескольких исходных файлов определения именованных констант и макроопределения могут быть собраны в одном включаемом файле и включены директивой #include во все исходные файлы. Включаемые файлы используются также для хранения объявлений внешних переменных и абстрактных типов данных, разделяемых несколькими исходными файлами.

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

Имя пути представляет собой имя файла, которому может предшествовать имя устройства и спецификация директории. Синтаксис имени пути определяется соглашениями операционной системы.

Препроцессор использует понятие стандартных директорий для поиска включаемых файлов. Стандартные директории задаются командой PATH операционной системы.

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

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

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

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

Затем препроцессор продолжает поиск в директориях, указанных в командной строке компиляции, и, наконец, ищет в стандартных директориях.

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

#include /* пример 1 */

#include "defs.h" /* пример 2 */

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

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

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

#define myinclude "c:\tc\include\mystuff.h"

#include myinclude

#include "myinclude.h"

Первая директива #include заставит препроцессор просматривать директорию C:\TC\INCLUDE\MYSTUFF.H, а вторая заставит искать файл MYINCLUDE.H в текущей директории.

Объединение символьных строк и склейку лексем в именованной константе, которая используется в директиве #include, использовать нельзя. Результат расширения константы должен сразу читаться как корректная директива #include.

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

В этом разделе описываются директивы, которые управляют условной компиляцией. Эти директивы позволяют исключить из процесса компиляции какие-либо части исходного файла посредством проверки условий (константных выражений).

Директивы #if, #elif, #else, #endif

Синтаксис:

Директива #if совместно с директивами #elif, #else и #endif управляет компиляцией частей исходного файла. Каждой директиве #if в том же исходном файле должна соответствовать завершающая ее директива #endif. Между директивами #if и #endif допускается произвольное количество директив #elif (в том числе ни одной) и не более одной директивы #else. Если директива #else присутствует, то между ней и директивой #endif на данном уровне вложенности не должно быть других директив #elif.

Препроцессор выбирает один из участков для обработки. может занимать более одной строки. Обычно это участок программного текста, однако это не обязательно: препроцессор можно использовать для обработки произвольного текста. Если содержит директивы препроцессора (в том числе и директивы условной компиляции), то эти директивы выполняются. Обработанный препроцессором текст передается на компиляцию.

Участок текста, не выбранный препроцессором, игнорируется на стадии препроцессорной обработки и не компилируется.

Препроцессор выбирает участок текста для обработки на основе вычисления, следующего за каждой директивой #if или #elif. Выбирается, следующий за со значением истина (не нуль), вплоть до ближайшей директивы #elif, #else, или #endif, ассоциированной с данной директивой #if.

Если ни одно ограниченное константное выражение не истинно, то препроцессор выбирает, следующий за директивой #else. Если же директива #else отсутствует, то никакой текст не выбирается.

Ограниченное константное выражение описано в разделе 4.2.9 "Константные выражения". Такое выражение не может содержать операцию sizeof (в СП ТС - может), операцию приведения типа, константы перечисления и плавающие константы, но может содержать препроцессорную операцию defined(). Эта операция дает истинное (не равное нулю) значение, если заданный в данный момент определен; в противном случае выражение ложно (равно нулю). Следует помнить, что идентификатор, определенный без значения, тем не менее рассматривается как определенный. Операция defined может использоваться в сложном выражении в директиве #if неоднократно:

#if defined(mysym) || defined(yoursym)

СП TC (в отличие от СП MSC) позволяет использовать операцию sizeof в ограниченном константном выражении для препроцессора. В следующем примере в зависимости от размера указателя определяется одна из констант - либо SDATA, либо LDATA:

#if (sizeof(void *) == 2)

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

/* пример 1 */

#if defined(CREDIT)

#elif defined (DEBIT)

/* пример 2 */

#define SIGNAL 1

#if STACKUSE == 1

#derine STACK 200

#define STACK 100

#define SIGNAL 0

#if STACKUSE == 1

#define STACK 100

#define STACK 50

/* пример 3 */

#elif DLEVEL == 1

#define STACK 100

#elif DLEVEL > 5

display(debugptr);

#define STACK 200

/* пример 4 */

#define REG 1 register

#define REG2 register

#if defined (M_86)

#ifdefined(M_68000)

#define REG4 register

В первом примере директивы #if, #elif, #else, #endif управляют компиляцией одного из трех вызовов функции. Вызов функции credit компилируется, если определена именованная константа CREDIT. Если определена именованная константа DEBIT, то компилируется вызов функции debit. Если ни одна из.именованных констант не определена, то компилируется вызов функции printerror. Следует учитывать, что CREDIT и credit являются различными идентификаторами в языке Си.

В следующих двух примерах предполагается, что константа DLEVEL предварительно определена директивой #define.

Во втором примере показаны два вложенных набора директив #if, #else, #endif. Первый набор директив обрабатывается, если значение DLEVEL больше 5. В противном случае обрабатывается второй набор.

В третьем примере директивы уловной компиляции используют для выбора текста значение константы DLEVEL. Константа STACK определяется со значением 0, 100 или 200, в зависимости от значения DLEVEL. Если DLEVEL больше 5, то компилируется вызов функции display, а константа STACK не определяется.

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

Компилятор обычно выделяет регистровую память переменным в том порядке, в котором записаны объявления переменных в программе. Если программа содержит больше объявлений переменных класса памяти register, чем имеется регистров в данной операционной среде, то регистровую память получат только те переменные, объявления которых записаны раньше. Следовательно, если более интенсивно будут использоваться те переменные, которые объявлены позже, выигрыш в эффективности от использования регистров окажется незначительным.

В примере показано, каким образом предоставить приоритет регистровой памяти наиболее важным переменным. Именованные константы REG1 и REG2 определяются как ключевые слова register. Они предназначены для объявления двух наиболее важных локальных переменных функции. Например, в следующем фрагменте программы такими переменными являются b и c.

func(REG3 int а)

Если определена константа М_86, препроцессор удаляет идентификаторы REG3 и REG4 из файла путем замены их на пустой текст. Регистровую память в этом случае получат только переменные b и с. Если определен идентификатор М_68000, то все четыре переменные объявляются с классом памяти register.

Если не определена ни одна из констант - ни М_86, ни М_68000, - то регистровую память получат переменные а, b и с.

Директивы #ifdef и #ifndef

Синтаксис:

Аналогично директиве #if, за директивами #ifdef и #ifndef может следовать набор директив #elif и директива #else. Набор должен быть завершен директивой #endif.

Использование директив #ifdef и #ifndef эквивалентно применению директивы #if, использующей выражение с операцией defined(). Эти директивы поддерживаются исключительно для совместимости с предыдущими версиями компиляторов языка Си. Для новых программ рекомендуется использовать директиву #if с операцией defined().

Когда препроцессор обрабатывает директиву #ifdef, он проверяет, определен ли в данный момент директивой #define. Если да, условие считается истинным, если нет - ложным.

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

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

Текущий номер строки и имя исходного файла доступны в программе через псевдопеременные с именами __LINE__ и __FILE__. Эти псевдопеременные могут быть использованы для выдачи во время выполнения сообщений о точном местоположении ошибки.

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

/* пример 1 */

#line 151 "copy.с"

/* пример 2 */

#define ASSERT(cond) if (!cond)\

{printf ("ошибка в строке %d файла %s\n", \

LINE__, __FILE__);} else;

В первом примере устанавливается имя исходного файла сору.с и текущий номер строки 151.

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

Директива обработки ошибок

В СП ТС реализована директива #error. Ее формат:

Обычно эту директиву записывают среди директив условной компиляции для обнаружения некоторой недопустимой ситуации. По директиве #error препроцессор прерывает компиляцию и выдает следующее сообщение:

Fatal: Error directive:

Fatal - признак фатальной ошибки; - имя исходного файла; - текущий номер строки; Error directive - сообщение об ошибке в директиве; - собственно текст диагностического сообщения.

Указания компилятору языка Си

Синтаксис:

Указания компилятору, или прагмы, предназначены для исполнения компилятором в процессе его работы. задает определенную инструкцию компилятору и, возможно, аргументы.

Набор прагм для каждого компилятора языка Си различен. Для получения подробной информации о прагмах смотрите системную документацию по используемому вами компилятору.

Псевдопеременные

Псевдопеременные представляют собой зарезервированные именованные константы, которые можно использовать в любом исходном файле. Каждый из них начинается и оканчивается двумя символами подчеркивания (__).

Номер текущей обрабатываемой строки исходного файла-десятичная константа. Первая строка исходного файла имеет номер 1.

Имя компилируемого исходного файла - символьная строка. Значение данной псевдопеременной изменяется каждый раз, когда компилятор обрабатывает директиву #include или директиву #line, а также по завершении включаемого файла.

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

Дата начала компиляции текущего исходного файла - символьная строка. Каждое вхождение __DATE__ в заданный файл дает одно и то же значение, независимо от того, как долго уже продолжается обработка. Дата имеет формат mmm dd УУУУ, где mmm - месяц (Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec), dd - число текущего месяца (1…31; в 1-й позиции dd ставится пробел, если число меньше 10), уууу - год (например, 1990).

Время начала компиляции текущего исходного файла - символьная строка. Каждое вхождение __TIME__ в заданный файл дает одно и то же значение, независимо от того, как долго уже продолжается обработка. Время имеет формат hh:mm:ss, где hh - час (00…23), mm - минуты (00…59), ss - секунды (00…59).

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

Директива #define

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

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

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

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

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

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

#include #include #include #include #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 ].

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

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

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

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

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

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

Директива #line

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