После того как выполнена макроподстановка, полученная строка вновь просматривается для поиска других имен констант и макроопределений. При повторном просмотре не принимается к рассмотрению имя ранее произведенной макроподстановки. Поэтому директива
#define х х
не приведет к зацикливанию препроцессора.
Примеры.
/* пример 1 */
#define WIDTH 80
#define LENGTH (WIDTH + 10)
/* пример 2 */
#define FILEMESSAGE "Попытка создать файл\
не удалась из-за нехватки дискового пространства"
/* пример 3 */
#define REG1 register
#define REG2 register
#define REG3
/* пример 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))?(1):(2)
а макровызов
MAX(i, s[i])
заменится на выражение
((i) >(s(i]))?(i):(s(i])
Обратите внимание на то, что в этом макроопределении аргументы с побочными эффектами могут привести к неверным результатам. Например, макровызов
MAX(i, s[i++])
заменится на выражение
((i)>(s[i++]))?(i):(s[i++])
Операнды операции > могут быть вычислены в любом порядке, а значение переменной i зависит от порядка вычисления. Поэтому результат выражения непредсказуем. Кроме того, возможна ситуация, когда переменная i будет инкрементирована дважды, что, вероятно, не требуется.
В пятом примере определяется макроопределение MULT. Макровызов MULT(3,5) в тексте программы заменяется на (3)*(5). Круглые скобки, в которые заключаются фактические аргументы, необходимы в тех случаях, когда аргументы макроопределения являются сложными выражениями. Например, макровызов
MULT(3+4,5+6)
заменится на (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)
Следующий фрагмент текста программы:
highval = 1024;
TRACE (highval);
примет после обработки препроцессором вид:
highval = 1024;
printf("highval" " = %d\n", highval);
Следующие друг за другом символьные строки рассматриваются компилятором языка Си в СП MSC версии 5 и в СП ТС как одна строка, поэтому полученная запись эквивалентна следующей:
Читать дальше