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

Форма для сдачи

Классическая схема компиляции

  1. Трансляция: текст на ЯП → объектный файл (результат трансляции — машинный код — и ссылки на требуемые внешние функции/переменные)
  2. Компоновка: объектные файлы (в т. ч. взятые из библиотек) → исполняемый файл

Си:

  1. Препроцессор
  2. Трансляция в язык Ассемблера
  3. Трансляция в объектный код
  4. Компоновка

Вычисления периода компиляции

int main(void) {
        int two = 13 + 14;
}

Атомарные макроопределения в роли констант

   1 #include <stdio.h>
   2 #define SIZE 12
   3 
   4 int code[SIZE];
   5 char message[SIZE+1];
   6 
   7 int main(void) {
   8         fgets(message, SIZE+1, stdin);
   9         for (int i=0; i<SIZE; i++) {
  10                 code[i] = message[i];
  11                 printf("%02d ", code[i]);
  12         }
  13         puts("");
  14 }

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

Встроенные макросы

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

$ touch пустой_файл.h
$ gcc -E -dM пустой_файл.h

Параметрические макросы

Есть два способа source code reuse:

:)) На что из начала семестра похожи макросы?

Если успеем

Макрос — не обязательно одно выражение, это может быть любая строка (несколько операторов, например)

Д/З (предварительное)

TODO Предполагается, что в некоторых задачах надо будет писать только макроопределения. Во время тестирования к файлу с решением будет приписан (сначала или в конце) текст функции main() с макроподстановками, и полученный генерат уже пойдёт на компиляцию и тестирование. Такие задачи будут начинаться словами «Написать макроопределение …». В этих задачах разрешается определять функции, а в макрос класть вызов этих функций, если так удобнее.

  1. Написать макроопределения SCREEN(имя, ширина, высота), DOT(x, y, c) и DISPLAY(имя). SCREEN(имя, ширина, высота) задаёт «текстовый экран» — двумерный массив имя типа char (заполненный точками). DOT(x, y, c) помещает на «экран» по координатам x, y символ c (координаты начинаются с 0 в левом нижнем углу). DISPLAY(имя) выводит экран построчно.

    • Подсказка. В DOT(x, y) как бы не хватает параметров «ширина» и «высота». Откуда их взять? Поскольку мы заводим локальный двумерный массив, его sizeof — это его площадь, а sizeof его нулевой строки — ширина.

  2. Написать макроопределения INIT(имя, количество), INPUT(имя) и FREE(имя). INIT(имя, количество) заводит локальный массив строк имя на количество элементов (изначально содержащий NULL-ы); INPUT(имя) вводит не более количества строк, а FREE(имя) — освобождает память, занятую этими строками. Тестирующая программа при этом можем выглядеть так:

    •    1 int main(void) {
         2         INIT(arr, 12);
         3         INPUT(arr);
         4         for(int i=0; i<12; i++)
         5                 puts(arr[i]?arr[i]:"EMPTY");
         6         FREE(arr);
         7         return 0;
         8 }
      
    • Подсказка. В INPUT(имя) как бы не хватает параметра «количество»? Откуда его взять? Поскольку мы заводим ''локальный массив'' строк (а не char **), от него можно брать sizeof (не забываем, что это количество байтов, а не количество элементов типа char *`)

    • Подсказка 2. В free() можно передавать NULL

  3. Написать программу, при компиляции которой будет задан макрос TYPENUM — чиcло от 1 до 4. 1 соответствует типу int, 2 — char, 3 — double и 4 — float. Программа должна будет ввести два значения соответствующего типа, сложить их и вывести результат. char вводится/выводится с помощью "%c"

  4. Написать программу, при компиляции которой будет задан макрос FORMULA(x) — некоторое выражение над переменной x типа double. Написать программу, которая в цикле вводит число y и выводит double x такое, что y == FORMULA(x). Гарантируется, что FORMULA(x) — монотонно возрастающая функция от x (например, x²). Конец ввода — EOF.

    • Указание 1. Если взять a<x и b>x, то для монотонной функции f() годится метод половинного деления: если f((a+b)/2) меньше y, то сделать a равным (a+b)/2, иначе b, и так до равенства

    • Указание 2. Для вещественных чисел не работает a==b, но функция nextafter() покажет, что между a и b никаких других чисел уже нет.

    • Указание 3. В float.h есть константа DBL_MAX — максимальное хранимое число типа double (минимальное — -DBL_MAX). Вы не поверите, но половинное деление сходится очень быстро.

LecturesCMC/AL/Prac/18_Preprocessor (последним исправлял пользователь hbd 2026-11-05 08:44:53)