46019 (665326), страница 22
Текст из файла (страница 22)
Данная строка удаляетлюбую ранее введенную последовательность лексем из идентификатора макроса;определение макроса теряется, и идентификатор его становится неопределенным.
Макрорасширения внутри строк #undef не выполняются.
Состояние определенности и неопределенности является важным свойством идентификатора, независимо от его фактического определения. Условные директивы препроцессора #ifdef и #ifndef, которые служат для проверки того, является ли идентификатор в текущий момент определенным, или нет, представляют собой гибкий механизм управления многими аспектами компиляции.
После того, как идентификатор макроса стал
неопределенным, он может бытьдалеепереопределендирективой
#define, с использованием той же самой или другой последовательности лексем.
#define BLOCK_SIZE 512
...
buff = BLOCK_SIZE*blks; /* расширяется в: 512*blks */ ...
#undef BLOCK_SIZE
/* использование BLOCK_SIZE теперь невозможно - это "неизвестный" препроцессору идентификатор */
...
#define BLOCK_SIZE 128 /*переопределение размера блока*/
...
buf = BLOCK_SIZE*blks; /* расширяется в: 128*blks */
...
Попыткапереопределения уже определенного идентификатора макроса приведет к сообщению уровня предупреждения, если только новоеопределения не повторяет текущее с точностью до последней лексемы. Предпочтительный способ работы с теми же определениями в других файлах заголовка следующий:
#ifndef BLOCK_SIZE
#define BLOCK_SIZE 512
#endif
Если идентификатор BLOCK_SIZE в текущий момент определен, то средняя строка не обрабатывается препроцессором; в противном же случае выполняется определение средней строки.
Отметим,что директива препроцессора не должна заканчиваться точкой с запятой (;). Любые символы, найденные препроцессором в последовательности лексем, включая точки с запятой, появятся в макрорасширениях. Последовательность лексем заканчивается первой встреченной новой строкой без предшествующего символа обратной наклонной черты. Любая последовательность пробельных символов, включая комментарии в последовательности лексем, заменяется на один символ пробела.
Программисты, привыкшие работать на языке ассемблера, должны преодолеть желание написать:
#define BLOCK_SIZE = 512 /* почему последовательность лексем включает символ = */
Опции -D и -U
Определение и отмена определения идентификаторов выполняется также при помощи опций компилятора командной строки - D и -U (см. Главу 4,"Компилятор командной строки" в Руководстве пользователя). Идентификаторы могут быть определены, но не могут бытьявно отменены, при помощи меню интегрированной среды разработки Options \! Compiler \! Defines (см. Главу 1,"Справочник по интегрированнойсредеразработки", также в Руководстве пользователя.)
Командная строка
tcc -Ddebug=1; paradox=0; X -Umysym myprog.c
эквивалентна помещению в программу строк:
#define debug 1
#define paradox 0
#define X
#undef mysym
Ключевые слова и защищенные слова
Допустимо, но не рекомендуется, использовать ключевые слова Turbo C++ в качестве идентификаторов макросов:
#define int long /* допустимо, но может привести к катастрофическим последствиям */
#define INT long /* допустимо и, вероятно, полезно */
Следующие предопределенные глобальные идентификаторы не могут появляться непосредственно следом за директивами #defineили #undef:
__STDC__ __DATE__
__FILE__ __TIME__
__LINE__
Отметим наличие в этих именах ведущих и хвостовых двойных символов подчеркивания.
Макросы с параметрами
Для определения макросов с параметрами используется следующий синтаксис:
#define идентификатор_макроса() последовательность-лексем
Любая запятая в круглых скобках внутри аргумента рассматривается как часть аргумента, а не какразделитель аргументов.
Отметим,что между идентификатором-макроса и левой круглой скобкой списка-аргументов не может находитьсяни одного пробельного символа. Опциональный список-аргументов -это последовательность идентификаторов, разделенных запятыми, как в списке аргументов функции С. Каждый разделенный запятой идентификаториграет рольформального аргумента, или же метки-заполнителя.
Вызов таких макросов выполняется записью
идентификатор-макроса()
в последующем исходном коде. Синтаксис вызова аналогичен синтаксису вызова функций; действительно, многиестандартные библиотечные "функции" С реализованы в виде макросов. Однако, имеется ряд возможных различий, которые могут привести к случайным ошибкам (см. стр.140 оригинала).
Опциональный список-фактических-аргументов должен содержать то же число разделяемых запятой лексем, известных как фактические аргументы, что содержится в списке-формальных-аргументов в строке с #define: каждому формальному аргументу должен соответствовать один фактический аргумент. Если число аргументах в двух указанных списков различно, то выдается сообщение об ошибке.
Вызов макроса приводитк двум типамзамены. Во-первых, идентификатор макроса и заключенные в круглые скобки аргументы заменяются последовательностью лексем. Затем формальные аргументы, найденные в данной последовательности лексем, заменяются соответствующими фактическими аргументами из списка-фактических-аргументов. Например,
#define CUBE(x) ((x)*(x)*(x))
...
int n,y
n = CUBE(y):
дает в результате следующую замену:
n = ((y)*(y)*(y));
Аналогичным образом, последняя строка в
#define SUM ((a) + (b))
...
int i,j,sum;
sum = SUM(i,j);
при расширении даст sum = ((i)+(j)). Причина кажущегося избытка круглых скобок станет очевидной, если рассмотреть пример:
n = CUBE(y+1);
Без внутренней пары круглых скобок в определении расширение даст запись: n=y+1*y+1*y+1, что при лексическом анализе равно:
n = y + (1*y) + (1*y) + 1; // если y не равен 0 или -3, то в // куб возводится (y+1) !
Как и в случае простых макроопределений, производится повторное сканирование текста для определения необходимости повторных макрорасширений получившихся макро-идентификаторов.
При использовании макросов со спискамиаргументов следует обратить внимание на следующие моменты:
1. Вложенные круглые скобки и запятые:
Список-фактических-аргументов может содержать вложенные круглые скобки, при условии соответствия числаоткрывающих числу закрывающих скобок; кроме того, запятые, заключенные во внутренние круглые скобки или кавычки, не рассматриваются в качестве разделителей аргументов:
#define ERRMSG(x, str) showerr("Error",x,str)
#define SUM(x,y) ((x) + (y))
...
ERRMSG(2, "Press Enter, then Esc");
// расширится в: showerr("Error",2,"Press Enter, then Esc"); */ return SUM(f(i,j), g(k.l));
// расширится в: return ((f(i,j)) + (g(k,l))); */
2. Склеивание лексем при помощи ##: можно выполнить склеивание (слияние) двух лексем, разделив их символами ## (и плюс опциональными пробельными символами с каждой стороны). Препроцессор удаляет пробельные символы и##, объединяядве отдельные лексемыв одну новуюлексему. Это средство можно использовать для конструированияидентификаторов; например, выполнив определение
#define VAR(i,j) (i##j)
и затем вызвав VAR(x,6), можно получить расширение (x6). Этот метод заменяет старый (не обеспечивающий мобильность кода) метод использования (i/**/j).
3. Преобразование к строкам при помощи #: символ #можно поместить перед формальным аргументом макроса с тем, чтобы после подстановкифактический аргумент был преобразован в строку. Поэтому, с учетом следующего определения макроса:
#define TRACE(flag) printf(#flag "=%d\n",flag)
фрагмент кода
int highval = 1024;
TRACE(highval);
станет равным
int highval = 1024;
printf("highval" "= %d\n", highval);
что в свою очередь будет рассматриваться как
int highval = 1024;
printf("highval=%d\n", highval);
4 Символ обратной наклонной черты для продолжения строки: длинная последовательность лексем может продлитьстрокупри помощи обратной наклонной черты (\). Символы обратной наклонной черты и новой строки оба вырезаются, и в результате образуется фактическая последовательность лексем, используемая в расширении:
#define WARN "фактически это одно\
строчное сообщение"
...
puts(WARN);
/* на экране будет: фактически это однострочное сообщение */
5. Побочные эффекты и прочие опасности: схожесть между вызовами макросов и функциями иногда скрывает различия между ними. При вызове макроса отсутствует встроенный контроль типов данных, поэтому различиев типах данных формального и фактического аргументов может вызвать непредсказуемые результаты, причину которых нелегко установить, причем относительно такой ошибки не будет выдано никаких предупреждений. При вызовах макросов могут также возникнуть нежелательные побочные эффекты, особенно когдафактический аргумент вычисляется более одного раза. Сравните CUBE и cube в следующем примере:
int cube(int x) (*
return x*x*x;
*)
#define CUBE(x) ((x)*(x)*(x))
...
int b =0, a = 3;
b = cube(a++);
/* cube() передается фактический аргумент = 3; поэтому b = 27, и теперь a = 4 */
a = 3;
b = CUBE(a++);
/* расширяется в: ((a++)*(a++)*(a++)); и теперь a = 6 */
Итоговое значение b зависит от того, что компилятор делает с расширенным выражением.
Включение файлов директивой #include
Директива #include подключает к исходному коду заданные в ней файлы, известные как включаемые файлы, файлы заголовковили заголовки. Синтаксис этой директивы имеет три формы:
#include
#include "имя_заголовка"
#include идентификатор_макроса
В данном случае угловые скобки являются фактически записываемымив тексте директивы лексемами, а не метасимволами, обозначающими, что имя_заголовка является опциональным.
Третий вариант записи предполагает, что ни символ <, ни символ " не являются первым не-пробельным символом, следующим за #include; кроме того, предполагается, что существует такое макроопределение, которое расширит идентификатор макроса в допустимое, следующее за разделителем имя заголовка в формате либо , либо "имя_заголовка".
Первый и второй варианты предполагают, что попыток макрорасширений сделано не будет; другими словами, имя_заголовка никогда не сканируется в поискахидентификаторов макросов. Имя_заголовка должно быть допустимым именем файла DOS с расширением (традиционно файлы заголовка имеют расширение .h) и опциональнымименемпути доступа к немус соответствующими разделителями.
Препроцессор удаляет строку #include и заменяет ее, начиная с текущей строки исходного кода, полным текстом файла заголовка. Сам исходный код остается без изменений,однакокомпилятор "видит" весь расширенный текст целиком. Таким образом, помещение в текст директивы #include может повлиять на контекст и продолжительность жизни любых идентификаторов во включаемых файлах.
Если поместить в имя_заголовка полное имя пути доступа к файлу, то поиск файла будет выполнентольков указанной таким образом директории.
Различиемежду форматами и "имя_заголовка" заключается в алгоритме поиска включаемого файла, применяемом в каждом случае; эти алгоритмы описаны в следующих двух разделах.
Поиск файла заголовка при формате
Вариант задает стандартный включаемый файл; поиск последовательно выполняется во всех включаемых директориях в той последовательности, в которой они определены. Если ни в одной из этих директорий по умолчанию искомый файл не найден, то выдается сообщение об ошибке.
Поиск файла заголовка при формате "имя_заголовка"
Вариант "имя_заголовка" задает включаемый файл, создаваемый пользователем; сначала выполняется его поиск в текущей директории (обычно в той директории, в которой находится исходный компилируемый файл). Если там файл не найден, то поиск продолжается во всех включаемых директориях, как в случае формата .
Приводимые ниже примеры поясняют описанные различия:
#include
/* заголовок из стандартной включаемой директории */
#define myinclude"c:\tc\include\mystuff.h"
/* Примечание: здесь допустимы одинарные символы обратной
наклонной черты; в операторе С пишется:
"c:\\tc\\include\\mystuff.h" */
#include myinclude
/* макрорасширение */
#include "myinclude.h"
/* макрорасширение отсутствует */
После расширения второй оператор#include заставит препроцессор искать нужный файл в C:\TC\INCLUDE\mystuff.h, и нигде более. Третий пример #includeзаставляет препроцессор выполнить поискmyinclude.h сначала в текущей директории, а затем во включаемых директориях.
Условная компиляция
Turbo C++ поддерживаетусловную компиляцию путем замены соответствующих строк исходного кода пустой строкой. Игнорируемые таким образом строки это те строки, что начинаются с символа # (за исключением директив #if, #ifdef, #ifndef, #else, #elif и #endif), а также любые строки, которые в результате выполнения директив не должны компилироваться. Все директивы условной компиляции должны завершаться в том же исходном или включаемом файле, где находится их начало.
Директивы условной компиляции #if, #elif, #else и #endif
Директивы условной компиляции #if, #elif, #else и #endif работают аналогично обыкновенным условным операторам
С.Они используются следующим образом:
#if выражение-1-типа-константы
...
#endif
...
Если выражение-1-типа-константы(для котороговыполняется макрорасширение) дает ненулевое значение (истина), то строки кода (возможно, пустые), представленногоразделом-1, которые могут представлять собой как командные строки препроцессора, так и обычные строки исходного кода, обрабатываются препроцессором и соответствующим образом передаются компилятору Turbo C++. В противном случае, если выражение-1-типа-константы дает нулевое значение (ложь), раздел-1игнорируется (макрорасширение и компиляция данного раздела не выполняются).
В случае "истина" после обработки раздела-1 препроцессором управление передается соответствующейдирективе #endif (которая заканчивает даннуюусловную конструкцию) и продолжается в следующем-разделе. В случае "ложь" управление передается следующей строке #elif (если она определена в данной конструкции),где вычисляетсявыражение-2-типа-константы. В случае "истины" обрабатывается раздел-2, после чего управление передается соответствующей директиве #endif, и т.д.,до тех пор, пока не будет достигнута последняя директива #else или #endif. Опциональная директива #else используется в качестве альтернативного условия в том случае, если все предыдущие проверки дали значение "ложь". Последовательность условных директив заканчивается директивой #endif.