Препроцессор, заголовочные файлы и make

⏱ 25 мин · 🧩 заданий: 5

Препроцессор

Перед компиляцией текст программы обрабатывает препроцессор. Он понимает строки, начинающиеся с #, и работает с текстом, ничего не зная о C.

#include

#include <sys/mman.h>     /* системный заголовок: mmap, PROT_*, MAP_* */
#include "mem.h"          /* свой заголовок: ищется рядом с файлом */

Директива просто вставляет содержимое файла. В заголовках лежат объявления: прототипы функций, типы, макросы. Тела функций — в .c-файлах или библиотеках.

«Include files has been skipped»

В задаче 8 заголовки опущены. Чтобы программа скомпилировалась, нужны: <stdio.h> (printf), <stdlib.h> (exit), <unistd.h> (fork, getpid, getppid, _exit), <sys/mman.h> (mmap), <sys/wait.h> (wait), <signal.h> (kill, SIGINT), <sys/types.h> (pid_t).

#define: константы и макросы

#define NUM_PROCS 5
#define BUF_SIZE (4 * 1024)
#define SQR(x) ((x) * (x))

Макрос — текстовая замена. Отсюда ловушки:

Макрос Вызов Что получится
#define SQR(x) x * x SQR(2 + 3) 2 + 3 * 2 + 3 = 11
#define SQR(x) ((x) * (x)) SQR(2 + 3) ((2 + 3) * (2 + 3)) = 25 ✓
#define SQR(x) ((x) * (x)) SQR(i++) ((i++) * (i++)) — двойной инкремент ✗

Многие «константы» системных вызовов — макросы: SIGINT, PROT_READ, MAP_SHARED, MAP_FAILED.

Условная компиляция

#ifdef DEBUG
    fprintf(stderr, "counter = %u\n", mem->counter);
#endif

#if defined(__linux__)
    /* код для Linux */
#else
    /* для других систем */
#endif
gcc -DDEBUG prog.c      # определить макрос DEBUG из командной строки

Защита от повторного включения

/* mem.h */
#ifndef MEM_H
#define MEM_H

typedef struct { int state; unsigned counter; pid_t pid, ppid; } Mem;
Mem *mem_create(void);

#endif

При втором #include "mem.h" макрос MEM_H уже определён, и содержимое пропускается. Многие компиляторы понимают и короткую форму #pragma once.

Раздельная компиляция

Большую программу делят на файлы:

main.c ──gcc -c──▶ main.o ─┐ ├──gcc──▶ prog mem.c ──gcc -c──▶ mem.o ─┘ ▲ mem.h (подключается в main.c и mem.c)
gcc -c main.c          # → main.o
gcc -c mem.c           # → mem.o
gcc main.o mem.o -o prog

Изменился один файл — перекомпилируем только его и заново компонуем.

Make

make автоматизирует сборку: по файлу Makefile он понимает зависимости и пересобирает только то, что устарело (сравнивая время изменения файлов).

CC     = gcc
CFLAGS = -Wall -Wextra -g

prog: main.o mem.o
    $(CC) main.o mem.o -o prog

main.o: main.c mem.h
    $(CC) $(CFLAGS) -c main.c

mem.o: mem.c mem.h
    $(CC) $(CFLAGS) -c mem.c

clean:
    rm -f *.o prog

.PHONY: clean

Формат правила: цель: зависимости, на следующей строке — табуляция и команда.

Где это встречается

Ядро Linux, coreutils и почти любой исходный пакет собираются через make. При сборке пакета из исходников (./configure && make && make install) работает именно этот механизм.

Итог

  • Препроцессор подставляет файлы и макросы до компиляции; макросы — просто текст, ставьте скобки.
  • Заголовки содержат объявления и защищаются #ifndef/#define/#endif.
  • Раздельная компиляция: .c → .o → компоновка.
  • make пересобирает только изменившееся по правилам из Makefile.

Практика решено 0 из 5

Вы не вошли — ответы проверяются, но прогресс не сохранится. Войти или зарегистрироваться.
Задание 1 Ввод ответа

Что напечатает программа?

#define SQR(x) x * x
printf("%d\n", SQR(2 + 3));
Задание 2 Написать код

Объявите макрос NUM_PROCS со значением 5 (как в задаче 8) и макрос MAX(a, b), возвращающий большее из двух, с правильной расстановкой скобок.

Задание 3 Написать код

Защитите заголовочный файл mem.h от повторного включения с помощью макроса MEM_H (#ifndef / #define / #endif).

Задание 4 Один вариант ответа

Чем отличаются #include <stdio.h> и #include "mem.h"?

Задание 5 Расставить по порядку

Make пересобирает программу из файлов main.c и mem.c. Изменился только mem.c. Расставьте действия make по порядку:

  1. Перекомпоновать: gcc main.o mem.o -o prog
  2. Сравнить время изменения mem.c и mem.o — исходник новее
  3. Увидеть, что main.o новее main.c — его не трогать
  4. Перекомпилировать: gcc -c mem.c -o mem.o

Войдите, чтобы отмечать пройденные уроки.