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 DEBUG
printf("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, и вы можете включать или отключать отладку в любое время во время компиляции.

Пример

#include <stdio.h>

// Определить константный макрос
#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;
}

Рекомендации по лучшим практикам

  1. Именование макросов:

    • Используйте все заглавные буквы и символы подчёркивания для именования макросов
    • Пример:#define MAX_SIZE 100
  2. Замечания о макросах, подобных функциям:

    • Каждый параметр и всё выражение должны быть заключены в круглые скобки.
    • Избегайте использования аргументов с побочными эффектами (например,SQUARE(x++))
  3. Защита заголовочных файлов:

    #ifndef MY_HEADER_H
    #define MY_HEADER_H
    /* 头文件内容 */
    #endif
  4. Отладка с помощью условной компиляции:

    #ifdef DEBUG
    #define DEBUG_PRINT(fmt, ...) printf(fmt, ##__VA_ARGS__)
    #else
    #define DEBUG_PRINT(fmt, ...)
    #endif
    
  5. Кроссплатформенная разработка:

    #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.

Давайте попробуем следующий пример:

Пример

#include <stdio.h> /** Демонстрационная программа предопределённых макросов * Демонстрирует распространённые предопределённые макросы стандарта ANSI C и их использование*/ int main() { // Вывести имя текущего исходного файла (строковая константа) printf("Текущий файл: %s\n", __FILE__); // Вывести дату компиляции (формат "MMM DD YYYY") printf("Дата компиляции: %s\n", __DATE__); // Вывести время компиляции (формат "HH:MM:SS") printf("Время компиляции: %s\n", __TIME__); // Вывести текущий номер строки (десятичное целое число) printf("Текущий номер строки: %d\n", __LINE__); // Проверить, соответствует ли он стандарту ANSI/ISO (1 означает соответствие) printf("Стандарт ANSI: %d\n", __STDC__); // Практический пример: вывод отладочной информации printf("\n[Отладочная информация] %s (строка %d), скомпилировано %s %s\n", __FILE__, __LINE__, __DATE__, __TIME__); return 0; }

Когда приведённый выше код (в файле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")

Оператор строкизации (#)

В определении макроса, когда нужно преобразовать параметр макроса в строковую константу, используйте оператор строкизации (#). Оператор, используемый в макросе, имеет определенный параметр или список параметров. Например:

Пример

#include <stdio.h> #define message_for(a, b) \ printf(#a " and " #b ": We love you!\n") int main(void) { message_for(Carole, Debra); return 0; }

Когда приведенный выше код скомпилирован и выполнен, он выдает следующие результаты:

Carole and Debra: We love you!

Оператор склеивания токенов (##)

Оператор склеивания токенов (##) в определении макроса объединяет два параметра. Он позволяет объединить два отдельных токена в определении макроса в один токен. Например:

Пример

#include <stdio.h> #define tokenpaster(n) printf ("token" #n " = %d", token##n) int main(void) { int token34 = 40; tokenpaster(34); return 0; }

Когда приведенный выше код скомпилирован и выполнен, он выдает следующие результаты:

token34 = 40

Вот как это происходит, потому что этот пример даст следующий фактический вывод компилятора:

printf ("token34 = %d", token34);

Этот пример демонстрирует, что token##n объединяется в token34; здесь мы использовалиОператор преобразования в строку (#)иОператор склеивания лексем (##)。

Оператор defined()

ПрепроцессорdefinedОператор используется в константных выражениях, чтобы определить, был ли идентификатор уже определён с помощью #define. Если указанный идентификатор определён, значение истинно (не равно нулю). Если указанный идентификатор не определён, значение ложно (равно нулю). Следующий пример демонстрирует использование оператора defined():

Пример

#include <stdio.h> #if !defined (MESSAGE) #define MESSAGE "Как бы не так!" #endif int main(void) { printf("Вот сообщение: %s\n", MESSAGE); return 0; }

Когда приведенный выше код скомпилирован и выполнен, он выдает следующие результаты:

Here is the message: You wish!

Параметризованные макросы

Мощная возможность CPP — это способность использовать параметризованные макросы для имитации функций. Например, следующий код вычисляет квадрат числа:

int square(int x) {
   return x * x;
}

Мы можем переписать приведённый выше код, используя макрос, следующим образом:

#define square(x) ((x) * (x))

Прежде чем использовать макрос с параметрами, необходимо использовать#defineдирективу, чтобы определить его. Список параметров заключается в круглые скобки и должен сразу следовать за именем макроса. Между именем макроса и левой круглой скобкой не допускается пробелов. Например:

Пример

#include <stdio.h> #define MAX(x,y) ((x) > (y) ? (x) : (y)) int main(void) { printf("Максимум между 20 и 10 равен %d\n", MAX(10, 20)); return 0; }

Когда приведенный выше код скомпилирован и выполнен, он выдает следующие результаты:

Max between 20 and 10 is 20
Другие расширения