C Препроцессор
Препроцессор C (CPP)— это независимая фаза процесса компиляции, на которойперед фактической компиляциейвыполняется текстовая обработка исходного кода. Основные возможности включают:
- Раскрытие макросов
- Включение файлов
- Условная компиляция
- Обработка специальных директив
Препроцессор CОн не является компонентом компилятора, а представляет собой отдельный этап процесса компиляции.
Короче говоря, препроцессор C — не что иное, как инструмент текстовой замены, который указывает компилятору выполнить необходимую предварительную обработку перед фактической компиляцией.
Мы будем сокращённо обозначать препроцессор C как CPP.
Все команды препроцессора начинаются с символа решётки#в начале. Он должен быть первым непробельным символом. Для удобочитаемости директивы препроцессора должны начинаться с первого столбца.
Все важные директивы препроцессора перечислены ниже:
| Директива | Описание | Примеры использования |
|---|---|---|
#define |
Определить макросы (символические константы или макросы, подобные функциям) | #define PI 3.14159#define MAX(a,b) ((a) > (b) ? (a) : (b)) |
#include |
Включение заголовочных файлов | #include <stdio.h>#include "myheader.h" |
#undef |
Отменить определение макроса | #undef PI |
#ifdef |
Компилировать следующий код, если макрос определен | #ifdef DEBUGprintf("Debug info\n");#endif |
#ifndef |
Если макрос не определен, компилировать последующий код (обычно используется для защиты заголовочных файлов) | #ifndef HEADER_H#define HEADER_H/* content */#endif |
#if |
Условная компиляция (можно использовать с оператором defined) | #if VERSION > 2/* new version code */#endif |
#else |
#if/#ifdef/#ifndefальтернативная ветвь |
#ifdef WIN32/* Windows代码 */#else/* other systems */#endif |
#elif |
Аналогично else if | #if defined(UNIX)/* Unix代码 */#elif defined(WIN32)/* Windows代码 */#endif |
#endif |
Завершить блок условной компиляции | Как показано в примере выше |
#error |
Сгенерировать ошибку компиляции и вывести сообщение | #if !defined(C99)#error "需要C99标准"#endif |
#pragma |
Директивы, специфичные для компилятора (нестандартные, различаются от компилятора к компилятору) | #pragma once#pragma pack(1) |
Примеры препроцессора
Проанализируйте следующие примеры, чтобы понять различные директивы.
#define MAX_ARRAY_LENGTH 20
Эта директива указывает CPP определить MAX_ARRAY_LENGTH как 20. Используйте#defineдля определения констант, чтобы улучшить читаемость.
#include <stdio.h> #include "myheader.h"
Эти директивы сообщают CPP, чтобыСистемная библиотекавзять stdio.h из неё и добавить текст в текущий исходный файл. Следующая строка сообщает CPP, чтобы взять его из локального каталогаmyheader.h, и добавить содержимое в текущий исходный файл.
#undef FILE_SIZE #define FILE_SIZE 42
Эта директива сообщает CPP, что нужно снять определение с уже определённого FILE_SIZE и определить его как 42.
#ifndef MESSAGE #define MESSAGE "You wish!" #endif
Эта директива сообщает CPP, что нужно определить MESSAGE только если MESSAGE не определён.
#ifdef DEBUG /* Your debugging statements here */ #endif
Эта директива сообщает CPP, что нужно выполнить обрабатывающие операторы, если DEBUG определён. Во время компиляции, если вы передадите ключ компилятору gcc,-DDEBUGэта директива очень полезна. Она определяет DEBUG, и вы можете включать или отключать отладку в любое время во время компиляции.
Пример
// Определить константный макрос
#define PI 3.1415926
#define GREETING "Hello, World!"
// Определить макрос, подобный функции (обратите внимание на использование круглых скобок)
#define SQUARE(x) ((x) * (x))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
// Пример условной компиляции
#define DEBUG 1
int main() {
// Использование константных макросов
printf("Значение PI: %f\n", PI);
printf("%s\n", GREETING);
// Использование макросов, подобных функциям
int x = 5;
printf("%d в квадрате: %d\n", x, SQUARE(x));
printf("Большее из 3 и 5: %d\n", MAX(3, 5));
// Пример условной компиляции
#ifdef DEBUG
printf("[Отладочная информация] Программа достигла функции main\n");
#endif
// Проверка версии компилятора
#if __STDC_VERSION__ >= 201112L
printf("Использование стандарта C11\n");
#elif __STDC_VERSION__ >= 199901L
printf("Использование стандарта C99\n");
#else
printf("Использование стандарта C89/C90\n");
#endif
// Пример директивы error (раскомментирование вызовет ошибку компиляции)
// #error "Это ошибка, вызванная вручную"
return 0;
}
Рекомендации по лучшим практикам
-
Именование макросов:
- Используйте все заглавные буквы и символы подчёркивания для именования макросов
- Пример:
#define MAX_SIZE 100
-
Замечания о макросах, подобных функциям:
- Каждый параметр и всё выражение должны быть заключены в круглые скобки.
- Избегайте использования аргументов с побочными эффектами (например,
SQUARE(x++))
-
Защита заголовочных файлов:
#ifndef MY_HEADER_H #define MY_HEADER_H /* 头文件内容 */ #endif
-
Отладка с помощью условной компиляции:
#ifdef DEBUG #define DEBUG_PRINT(fmt, ...) printf(fmt, ##__VA_ARGS__) #else #define DEBUG_PRINT(fmt, ...) #endif
-
Кроссплатформенная разработка:
#if defined(_WIN32) // Windows特定代码 #elif defined(__linux__) // Linux特定代码 #elif defined(__APPLE__) // macOS特定代码 #endif
Предопределённые макросы
ANSI C определяет множество макросов. Вы можете использовать эти макросы в программировании, но не можете напрямую изменять эти предопределённые макросы.
| Макрос | Описание |
|---|---|
| __DATE__ | Текущая дата, строковая константа в формате "MMM DD YYYY". |
| __TIME__ | Текущее время, строковая константа в формате "HH:MM:SS". |
| __FILE__ | Содержит имя текущего файла, строковую константу. |
| __LINE__ | Содержит номер текущей строки, десятичную константу. |
| __STDC__ | Определяется как 1, когда компилятор выполняет компиляцию в соответствии со стандартом ANSI. |
Давайте попробуем следующий пример:
Пример
Когда приведённый выше код (в файлеtest.c) будет скомпилирован и выполнен, он выдаёт следующие результаты:
当前文件: predef_macros.c 编译日期: Jul 5 2023 编译时间: 14:30:45 当前行号: 13 ANSI标准: 1 [调试信息] predef_macros.c (第16行) 编译于 Jul 5 2023 14:30:45
Операторы препроцессора
Препроцессор C предоставляет следующие операторы, помогающие создавать макросы:
Оператор продолжения макроса (\\)
Макрос обычно записывается в одной строке. Но если макрос слишком длинный, чтобы уместиться в одной строке, используйте оператор продолжения строки (\\). Например:
#define message_for(a, b) \
printf(#a " and " #b ": We love you!\n")
Оператор строкизации (#)
В определении макроса, когда нужно преобразовать параметр макроса в строковую константу, используйте оператор строкизации (#). Оператор, используемый в макросе, имеет определенный параметр или список параметров. Например:
Пример
Когда приведенный выше код скомпилирован и выполнен, он выдает следующие результаты:
Carole and Debra: We love you!
Оператор склеивания токенов (##)
Оператор склеивания токенов (##) в определении макроса объединяет два параметра. Он позволяет объединить два отдельных токена в определении макроса в один токен. Например:
Пример
Когда приведенный выше код скомпилирован и выполнен, он выдает следующие результаты:
token34 = 40
Вот как это происходит, потому что этот пример даст следующий фактический вывод компилятора:
printf ("token34 = %d", token34);
Этот пример демонстрирует, что token##n объединяется в token34; здесь мы использовалиОператор преобразования в строку (#)иОператор склеивания лексем (##)。
Оператор defined()
ПрепроцессорdefinedОператор используется в константных выражениях, чтобы определить, был ли идентификатор уже определён с помощью #define. Если указанный идентификатор определён, значение истинно (не равно нулю). Если указанный идентификатор не определён, значение ложно (равно нулю). Следующий пример демонстрирует использование оператора defined():
Пример
Когда приведенный выше код скомпилирован и выполнен, он выдает следующие результаты:
Here is the message: You wish!
Параметризованные макросы
Мощная возможность CPP — это способность использовать параметризованные макросы для имитации функций. Например, следующий код вычисляет квадрат числа:
int square(int x) {
return x * x;
}
Мы можем переписать приведённый выше код, используя макрос, следующим образом:
#define square(x) ((x) * (x))
Прежде чем использовать макрос с параметрами, необходимо использовать#defineдирективу, чтобы определить его. Список параметров заключается в круглые скобки и должен сразу следовать за именем макроса. Между именем макроса и левой круглой скобкой не допускается пробелов. Например:
Пример
Когда приведенный выше код скомпилирован и выполнен, он выдает следующие результаты:
Max between 20 and 10 is 20Другие расширения