Введение
Препроцессор Си выступает начальным звеном конвейера компиляции, преобразуя исходный код до его передачи синтаксическому анализатору. В настоящем руководстве подробно рассматриваются практические приемы работы с автономной утилитой cpp в среде Ubuntu. Вы научитесь анализировать подстановку макросов, отслеживать ветвление при условной компиляции и задавать пользовательские каталоги заголовочных файлов, что критически важно при отладке сложных сборочных сценариев.
Проверка наличия и базовой конфигурации утилиты cpp
Для развертывания инструментария препроцессинга установим пакет компилятора GCC, выполнив обновление метаданных репозиториев и установку пакетов:
apt-get update -qq && apt-get install -y gcc

Убедимся в наличии исполняемого файла препроцессора в системных каталогах поиска с помощью команды which cpp:
Расположение бинарного файла /usr/bin/cpp подтверждает корректную интеграцию утилиты в системное окружение Linux.

Определим установленную версию и параметры сборки GNU C Preprocessor посредством cpp --version:
В терминале отображаются сведения об успешной готовности версии cpp 13.3.0 к выполнению задач.
⚙️ [Ключевые параметры утилиты]
-D name : создает макроопределение, используемое при условной компиляции и для инициализации констант через командную строку.-U name : отменяет действие указанного системного или предварительно объявленного макроса.-I dir : включает дополнительную директорию в перечень путей поиска заголовочных файлов (#include).-dM : выводит полный список всех активных макросов, определенных на момент завершения препроцессинга.-dD : оставляет директивы #define непосредственно в сгенерированном результирующем коде.-P : отключает генерацию отладочной информации о позициях и номерах исходных строк (#line).-v : выводит расширенный протокол работы, включая последовательность каталогов для включения библиотечных заголовков.

Развертывание макросов и анализ подстановок в коде
Подготовим изолированное рабочее пространство для экспериментов, выполнив переход во вновь созданный каталог:
mkdir -p ~/cpp_practice && cd ~/cpp_practice

Сформируем тестовый исходный файл на Си, содержащий как макроконстанту, так и параметризованное макроопределение функции:
printf '#define BUFFER_SIZE 1024\n#define SQUARE(x) ((x) * (x))\nint limit = BUFFER_SIZE;\nint area = SQUARE(5);\n' > macro_test.c

Запустим обработку исходника утилитой cpp с направлением преобразованного листинга в файл output.i:
cpp macro_test.c output.i
Препроцессор выполнит все замены и сохранит полностью развернутый текст программы.

Отсечем вспомогательные директивы синхронизации строк с помощью фильтрации grep, чтобы изучить результат преобразования:
cat output.i | grep -v '^#'
В выводе видно, что идентификатор BUFFER_SIZE заменился значением 1024, а вызов SQUARE(5) трансформировался в вычисляемое арифметическое выражение ((5) * (5)).

Управление условной компиляцией через параметры -D и -U
Вернемся в рабочий каталог для тестирования ветвлений препроцессора:
cd ~/cpp_practice

Создадим код с условием #ifdef, поведение которого зависит от наличия флага отладки:
printf '#ifdef DEBUG\nconst char *status = "DEBUG_ENABLED";\n#else\nconst char *status = "PRODUCTION";\n#endif\n' > condition_test.c

Передадим макрос через аргумент -DDEBUG, активировав опцию -P для удаления отладочных маркеров:
cpp -DDEBUG -P condition_test.c
Благодаря внешней инъекции макроопределения условный блок переключается на ветку истинности, формируя объявление const char *status = "DEBUG_ENABLED";.

Проверим альтернативный сценарий сборки, принудительно сняв определение макроса ключом -UDEBUG:
cpp -UDEBUG -P condition_test.c
В результате препроцессор активирует блок #else, транслируя в итоговый листинг строку const char *status = "PRODUCTION";.

Настройка путей к заголовкам и подавление меток строк (-I, -P)
Для работы с заголовочными файлами перейдем в тестовое окружение:
cd ~/cpp_practice

Выделим пользовательские заголовочные файлы в отдельную структуру папок:
mkdir -p custom_headers

Сформируем заголовочный файл version.h, зафиксировав в нем строковую константу версии:
echo '#define APP_VERSION "1.0.0"' > custom_headers/version.h

Подготовим основной файл программы, подключающий вынесенный заголовок:
printf '#include "version.h"\nconst char *ver = APP_VERSION;\n' > main_app.c

Вызовем препроцессор с указанием пути поиска заголовков -I./custom_headers и ключом очистки маркеров -P:
cpp -I./custom_headers -P main_app.c
Параметр -I позволяет утилите обнаружить требуемый файл version.h в изолированной папке, а флаг -P устраняет служебные комментарии, предоставляя финальную строку const char *ver = "1.0.0";.

Мы рассмотрели практические методы использования препроцессора cpp в ОС Ubuntu. Прямой анализ результатов трансформации кода помогает быстро локализовать нетривиальные ошибки в макросах и лучше понимать поведение сборочных цепочек Си/C++.