КАТЕГОРИИ: Архитектура-(3434)Астрономия-(809)Биология-(7483)Биотехнологии-(1457)Военное дело-(14632)Высокие технологии-(1363)География-(913)Геология-(1438)Государство-(451)Демография-(1065)Дом-(47672)Журналистика и СМИ-(912)Изобретательство-(14524)Иностранные языки-(4268)Информатика-(17799)Искусство-(1338)История-(13644)Компьютеры-(11121)Косметика-(55)Кулинария-(373)Культура-(8427)Лингвистика-(374)Литература-(1642)Маркетинг-(23702)Математика-(16968)Машиностроение-(1700)Медицина-(12668)Менеджмент-(24684)Механика-(15423)Науковедение-(506)Образование-(11852)Охрана труда-(3308)Педагогика-(5571)Полиграфия-(1312)Политика-(7869)Право-(5454)Приборостроение-(1369)Программирование-(2801)Производство-(97182)Промышленность-(8706)Психология-(18388)Религия-(3217)Связь-(10668)Сельское хозяйство-(299)Социология-(6455)Спорт-(42831)Строительство-(4793)Торговля-(5050)Транспорт-(2929)Туризм-(1568)Физика-(3942)Философия-(17015)Финансы-(26596)Химия-(22929)Экология-(12095)Экономика-(9961)Электроника-(8441)Электротехника-(4623)Энергетика-(12629)Юриспруденция-(1492)Ядерная техника-(1748) |
Общие сведения. В интегрированную среду подготовки программ на Си или в компилятор языка как обязательный компонент входит препроцессор
В интегрированную среду подготовки программ на Си или в компилятор языка как обязательный компонент входит препроцессор. Назначение препроцессора - обработка исходного текста программы до ее компиляции. Препроцессорная обработка включает несколько стадий, выполняемых последовательно. Конкретная реализация может объединять несколько стадий, но результат должен быть таким, как если бы они выполнялись в следующем порядке: Все системно-зависимые обозначения перекодируются в стандартные коды. Каждая пара из символов '\' и "конец строки" вместе с пробелами между ними убираются, и тем самым следующая строка исходного текста присоединяется к строке, в которой находилась эта пара символов. В тексте распознаются директивы и лексемы препроцессора, а каждый комментарий заменяется одним символом пустого промежутка. Выполняются директивы препроцессора и производятся макроподстановки. Эскейп-последовательности в символьных константах и символьных строках заменяются на их эквиваленты. Смежные символьные строки конкатинируются, то есть соединяются в одну строку. Каждая препроцессорная лексема преобразуется в текст на языке Си. Поясним, что понимается под препроцессорными лексемами или лексемами препроцессора. К ним относятся символьные константы, имена включаемых файлов, идентификаторы, знаки операций, препроцессорные числа, знаки препинания, строковые константы и любые символы, отличные от пробела. Стадия обработки директив препроцессора. При ее выполнении возможны следующие действия: замена идентификаторов заранее подготовленными последовательностями символов; включение в программу текстов из указанных файлов; исключение из программы отдельных частей ее текста, условная компиляция; макроподстановка, то есть замена обозначения параметризованным текстом, формируемым препроцессором с учетом конкретных аргументов. Символические константы: #define Если в качестве первого символа в строке программы используется символ #, то эта строка является командной строкой препроцессора (макропроцессора). Командная строка препроцессора заканчивается символом перевода на новую строку. Если непосредственно перед концом строки поставить символ обратной косой черты "\", то командная строка будет продолжена на следующую строку программы. Директива #define, подобно всем директивам препроцессора, начинается c символа # в самой левой позиции. Она может появиться в любом месте исходного файла, а даваемое определение имеет силу от места появления до конца файла. Мы активно используем эту директиву для определения символических констант в наших примерах программ, однако она имеет более широкое применение, что мы покажем дальше. Замена идентификаторов #define идентификатор строка Пример: #define ABC 100 Заменяет каждое вхождение идентификатора ABC в тексте программы на 100: #undef идентификатор Пример: #undef ABC Отменяет предыдущее определение для идентификатора ABC. Пример: /* Простые примеры директивы препроцессора */ #define TWO 2 /* можно использовать комментарии*/ #define MSG "Текст 1.\ Продолжение текста 1" /* обратная косая черта продолжает определение на следующую строку */ #define FOUR TWO*TWO #define PX printf("X равен %d.\n", x) #define FMT "X равен %d.\n" main() { int x = TWO; PX; x = FOUR; printf(FMT,x); printf("%s\n",MSG); printf("TWO:MSG\n"); } В результате выполнения нашего примера будем иметь: X равен 2 X равен 4 Текст 1. Продолжение текста 1 TWO: MSG Разберем, что произошло. Оператор int x = TWO; превращается в int x = 2; Затем оператор PX; превращается в printf("X равно %d.\n",x); поскольку сделана полная замена. Теперь мы видим, что макроопределение может представлять любую строку, даже целое выражение на языке Си. Заметим, что это константная строка. PX напечатает только переменную, названную x. В следующей строке выполняется следующее: x = FOUR; превращается x = TWO*TWO; превращается в x = 2*2; и на этом все заканчивается. Фактическое умножение имеет место не во время работы препроцессора и не при компиляции, а всегда без исключения при работе программы. Препроцессор не выполняет вычислений. Он только очень точно делает предложенные подстановки. Заметим, что макроопределение может включать другие определения. Некоторые компиляторы не поддерживают это свойство вложения. В следующей строке printf(FMT,x); превращается в 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)) Каждое вхождение выражения 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) main() { int x = 4; int z; z = SQUARE(x); PR(z); z = SQUARE(2); PR(z); PR(SQUARE(x)); PR(SQUARE(x+2)); PR(100/SQUARE(2)); PR(SQUARE(++x)); } Всюду, где в нашей программе появляется макроопределение 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 переменная замещается соответствующим аргументом. Все аргументы в этом определении замещаются. Рассмотрим третью строку: PR(SQUARE(x)); Она становится следующей строкой: printf("SQUARE(x) равно %d.\n", SQUARE(x)); после первого этапа макрорасширения. Второе 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. Запомним! В макроопределении нет пробелов, но они могут появиться в замещающей строке. Препроцессор полагает, что макроопределение заканчивается на первом пробеле, поэтому все, что стоит после пробела, остается в замещающей строке. Используйте круглые скобки для каждого аргумента и всего определения. Это является гарантией того, что элементы будут сгруппированы надлежащим образом в выражении. Для имен макрофункций следует использовать прописные буквы. Это соглашение не распространяется так широко, как соглашение об использовании прописных букв для макроконстант. Их применение предостережет от возможных побочных эффектов макроопределений. Предположим, что мы разработали несколько макрофункций по своему усмотрению. Если мы пишем новую программу, мы не должны их переопределять. Нужно использовать директиву #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. Угловые скобки означают, что файл math.h будет взят из некоторого стандартного каталога (обычно это /usr/include). Текущий каталог не просматривается: #include "имя_файла" Пример: #include "ABC" Препроцессор заменяет эту строку содержимым файла ABC. Так как имя файла заключено в кавычки, то поиск производится в текущем каталоге (в котором содержится основной файл исходного текста). Если в текущем каталоге данного файла нет, то поиск производится в каталогах, определенных именем пути в опции -l препроцессора. Если и там нет файла, то просматривается стандартный каталог. В операционной системе UNIX угловые скобки сообщают препроцессору, что файл следует искать в одном или нескольких стандартных системных каталогах. Кавычки говорят ему, что сначала нужно смотреть в вашем каталоге или в каком-то другом, если вы определяете его именем файла, а затем искать в стандартных местах. В конкретных реализациях количество и наименования заголовочных файлов могут быть разными: #include <stdio.h> ищет в системном каталоге #include "my.h" ищет в текущем рабочем каталоге #include "/user/1/my.h" ищет в каталоге /user/1 В типичной микропроцессорной системе эти две формы являются синонимами, и препроцессор ведет поиск на указанном диске. #include "stdio.h" ищет на стандартном диске #include <stdio.h> ищет на стандартном диске #include "a:stdio.h" ищет на диске а По соглашению суффикс.h используется для заголовочных файлов, т.е. файлов с информацией, которая располагается в начале программы. Заголовочные файлы обычно состоят из операторов препроцессора. Некоторые файлы включены в систему, например, stdio.h, но можно создать и свой файл. Многие программисты разрабатывают свои стандартные заголовочные файлы, чтобы использовать их в программах.
Дата добавления: 2014-01-05; Просмотров: 367; Нарушение авторских прав?; Мы поможем в написании вашей работы! Нам важно ваше мнение! Был ли полезен опубликованный материал? Да | Нет |