Визначення макроса має форму
#define назва текст-заміни
Це вимагає найпростішої заміни макроса — наступні появи лексеми «назва» буде замінено «текстом заміни». Назва в #define має ту саму форму, що й назви змінних; текст заміни може бути довільним. Зазвичай текст заміни розміщено на тому самому рядкові, але якщо треба продовжити його на наступних, додайте зворотню похилу \ вкінці кожного рядка, який ви хочете продовжити. Зона дії назви, означеної #define, починаєтьсся з пункту, де її було означено і аж до кінця файла, який буде компільовано. Визначення можуть вживати попередні визначення. Заміни матимуть місце тільки для лексем і не відбуваються всередині ланцюжків взятих у лапки. Скажімо, якщо YES — це означена назва, заміна стане неможливою у випадку printf("YES")або YESMAN. Назву можна пов'язати з будь-яким текстом заміни. Наприклад
#define forever for(;;) /* нескінчений цикл */
визначає для нескінченого циклу нове слово — forever.
Можна також означити макроси з аргументами, тож текст заміни зможе відрізнятися для різних викликів макроса. Для прикладу, створимо макрос під назвою max:
#define max(A, B) ((A) > (B) ? (A) : (B))
Хоч це й виглядає ніби виклик функції, виклик max розкривається у вбудований код. Кожна поява формального параметра (у цьому випадку A й B) замінюватиметься на відповідні дійсні аргументи. Таким чином, рядок
x = max(p+q, r+s);
замінюється на
x = ((p+q) > (r+s) ? (p+q) : (r+s));
Доки аргументи вживаються несуперечливо, цей макрос служитиме для будь-якого типу даних; немає потреби у відмінному max для іншого типу даних, як це відбувається у функціях.
Якщо ви розглянете розкриття max, то можете помітити деякі пастки. Вирази обчислено двічі; це може бути погано, якщо вони включатимуть побічні ефекти, такі як оператори приросту або ввід і вивід. Так, наприклад,
max(i++, j++); /* НЕПРАВИЛЬНО */
здіснить приріст більшого значення двічі. Треба також звертати увагу на дужки, щоб упевнитись, що порядок обчислень збережено; уявіть, що станеться якщо макрос
#define square(x) x * x /* НЕПРАВИЛЬНО */
викликати як square(z+1).
Не зважаючи на це, макроси корисні. Один з прикладів їхнього використання на практиці можна знайти в <stdio.h>, де getchar і putchar часто означено як макроси, щоб запобігти втраті робочого часу на виклик функції для кожного опрацьованого знака. Функції у <ctype.h>також часто втілено як макроси.
Назви можна скасувати за допомогою #undef, зазвичай, щоб упевнитись, що якась функція є справді функцією а не макросом:
#undef getchar
int getchar(void) { ... }
Формальні праметри не замінюються, якщо іх було взято у лапки при означенні макроса. Проте, якщо попереду назви параметра стоїть знак # у тексті заміни, комбінація розшириться до взятого в лапки ланцюжка, у якому параметр заміниться на фактичний аргумент. Це можна комбінувати зі зчепленням ланцюжків, наприклад для відлагодження макросу виводу:
#define dprint(expr) printf(#expr " = %g\n", expr)
Якщо його викликати як
dprint(x/y)
макрос розкриється у
printf("x/y" " = %g\n", x/y);
ланцюжки буде зчеплено, тож отримаємо
printf("x/y = %g\n", x/y);
Всередині отриманого аргументу, кожний знак " замінюється на \" і кожний \ на \\, тож результат буде допустимою ланцюжковою константою.
Оператор препроцесора ## дає можливість зчепити дійсні аргументи під час розкриття макросу. Якщо параметр у тексті заміни є суміжним з ##, цей параметр буде замінено на дійсний аргумент, ## і оточуючі пробіли усунуто і результат переглянуто. Наприклад, наступний макросpaste зчеплює власні два аргументи:
#define paste(front, back) front ## back
тож paste(name, 1) створить лексему name1.
Правила гніздованого використання ## досить заплутані; подальші деталі ви знайдете у Додатку А.
Вправа 4-14. Означте макрос swap(t,x,y), який міняє місцями два аргументи типу t. (Тут поможе блокова структура коду.)