Препроцессор — это специальная программа, являющаяся частью компилятора языка Си. Она предназначена для предварительной обработки текста программы. Препроцессор позволяет включать в текст программы файлы и вводить макроопределения.
Работа препроцессора осуществляется с помощью специальных директив (указаний). Они отмечаются знаком решетка #. По окончании строк, обозначающих директивы в языке Си, точку с запятой можно не ставить.Основные директивы препроцессора
- #include — вставляет текст из указанного файла
- #define — задаёт макроопределение (макрос) или символическую константу
- #undef — отменяет предыдущее определение
- #if — осуществляет условную компиляцию при истинности константного выражения
- #ifdef — осуществляет условную компиляцию при определённости символической константы
- #ifndef — осуществляет условную компиляцию при неопределённости символической константы
- #else — ветка условной компиляции при ложности выражения
- #elif — ветка условной компиляции, образуемая слиянием else и if
- #endif — конец ветки условной компиляции
- #line — препроцессор изменяет номер текущей строки и имя компилируемого файла
- #error — выдача диагностического сообщения
- #pragma — действие, зависящее от конкретной реализации компилятора.
Директива #include
Директива #include позволяет включать в текст программы указанный файл.
Если заголовочный файл содержит описание библиотечных функций и находится в папке компилятора, он заключается в угловые скобки <>.
Если файл находится в текущем каталоге проекта, он указывается в кавычках «». Для файла, находящегося в другом каталоге необходимо в кавычках указать полный путь.
#include <stdio.h>
#include "func.h"
Директива #define
Директива #define позволяет вводить в текст программы константы и макроопределения.
Общая форма записи
Поля Идентификатор и Замена разделяются одним или несколькими пробелами.
Директива #define указывает компилятору, что нужно подставить строку, определенную аргументом Замена, вместо каждого аргумента Идентификатор в исходном файле.
Идентификатор не заменяется, если он находится в комментарии, в строке или как часть более длинного идентификатора.
2
3
4
5
6
7
8
#include <stdio.h>
#define A 3
int main()
{
printf("%d + %d = %d", A, A, A+A); // 3 + 3 = 6
getchar();
return 0;
}
В зависимости от значения константы компилятор присваивает ей тот или иной тип. С помощью суффиксов можно переопределить тип константы:
- U или u представляет целую константу в беззнаковой форме (unsigned);
- F (или f) позволяет описать вещественную константу типа float;
- L (или l) позволяет выделить целой константе 8 байт (long int);
- L (или l) позволяет описать вещественную константу типа long double
#define B 280LU // unsigned long int
#define C 280 // int (long int)
#define D 280L // long int
#define K 28.0 // double
#define L 28.0F // float
#define M 28.0L // long double
Вторая форма синтаксиса определяет макрос, подобный функции, с параметрами. Эта форма допускает использование необязательного списка параметров, которые должны находиться в скобках. После определения макроса каждое последующее вхождение
замещается версией аргумента замена, в которой вместо формальных аргументов подставлены фактические аргументы.
Пример на Си: Вычисление синуса угла
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#define PI 3.14159265
#define SIN(x) sin(PI*x/180)
int main()
{
int c;
system("chcp 1251");
system("cls");
printf("Введите угол в градусах: ");
scanf("%d", &c);
printf("sin(%d)=%lf", c, SIN(c));
getchar(); getchar();
return 0;
}
Результат выполнения
Отличием таких макроопределений от функций в языке Си является то, что на этапе компиляции каждое вхождение идентификатора замещается соответствующим кодом.
Таким образом, программа может иметь несколько копий одного и того же кода, соответствующего идентификатору. В случае работы с функциями программа будет содержать 1 экземпляр кода, реализующий указанную функцию, и каждый раз при обращении к функции ей будет передано управление.
Отменить макроопределение можно с помощью директивы #undef.
Однако при использовании таких макроопределений следует соблюдать осторожность, например
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
#define sum(A,B) A+B
int main()
{
int a, b, c, d;
a = 3; b = 5;
c = (a + b) * 2; // c = (a + b)*2
d = sum(a, b) * 2; // d = a + b*2;
printf(" a = %d\n b = %d\n", a, b);
printf(" c = %d \n d = %d \n", c, d);
getchar();
return 0;
}
Результат выполнения
По умолчанию текст макроопределения должен размещаться на одной строке. Если требуется перенести текст макроопределения на новую строку, то в конце текущей строки ставится символ «обратный слеш» — \.
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
#define sum(A,B) A + \
B
int main()
{
int a, b, c, d;
a = 3; b = 5;
c = (a + b) * 2; // c = (a + b)*2
d = sum(a, b) * 2; // d = a + b*2;
printf(" a = %d\n b = %d\n", a, b);
printf(" c = %d \n d = %d \n", c, d);
getchar();
return 0;
}
Кроме того, директива #define позволяет замещать часть идентификатора. Для указания замещаемой части используется ##.
2
3
4
5
6
7
8
9
#include <stdio.h>
#define SUM(x,y) (a##x + a##y)
int main()
{
int a1 = 5, a2 = 3;
printf("%d", SUM(1, 2)); // (a1 + a2)
getchar();
return 0;
}
Условная компиляция
Директивы #if или #ifdef/#ifndef вместе с директивами #elif, #else и #endif управляют компиляцией частей исходного файла.
Если указанное выражение после #if имеет ненулевое значение, в записи преобразования сохраняется группа строк, следующая сразу за директивой #if. Синтаксис условной директивы следующий:
2
3
4
5
6
7
группа операций
#elif константное выражение
группа операций
#else
группа операций
#endif
Отличие директив #ifdef/#ifndef заключается в том, что константное выражение может быть задано только с помощью #define.
каждой директивы #if в исходном файле должна быть соответствующая закрывающая директива #endif.
Между директивами #if и #endif может располагаться любое количество директив #elif, однако допускается не более одной директивы #else.
Директива #else, если присутствует, должна быть последней перед директивой #endif.
Пример
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
#include <stdlib.h>
#define P 2
int main()
{
system("chcp
1251");
system("cls");
#if P==1
printf("Выполняется ветка 1");
#elif P==2
printf("Выполняется ветка 2, P=%d", P);
#else
printf("Выполняется другая ветка, P=%d", P);
#endif
getchar();
return 0;
}
Результат выполнения
Допустима такая конструкция в BCB6
2
3
4
UNUSED int deli[4] = {0,1,1,0}, delj[4] = {0,0,1,1}, npan = 6; \
UNUSED double longs_pan[6] = {260.00, 272.59, 276.50, 278.65, 280.73, 292.0},\
lats_pan[6] = { 19.55, 13.97, 9.60, 8.10, 9.33, 3.4}
Здравствуйте ув. Елена ! Подскажите пожалуйста какой будет результат. (0x0319 && 0x0000)=? . Мне нужно задать координаты а одна из координат всегда 0 . но другая 319 значит пиксели нужно рисовать от Х0 до Х319 . Заранее спасибо!
Результат будет 0.
И не совсем понятно, при чем тут координаты.
Добрый день! Подскажите, пожалуйста, как сделать так, чтобы макроопределение #define объявлялось только в одном файле проекта, а использовалось во всех? Возможно ли это?
Определите в заголовочном файле (.h) и подключите его во всех файлах проекта
Спасибо большое, Вы мне очень помогли!
добрый день , подскажите пожалуйста зачем использовать директивы #if, #else и.тд, если они по сути не отличаются от обычных операторов ветвления( объясните суть пж)
Директивы работают на этапе препроцессора, а не на этапе компиляции.
Например, нужно подключить нужный файл библиотеки с описаниями регистров микроконтроллера в зависимости от выбранного типа микроконтроллера, для которого компилируется программа.
Благодарю! За Ваш труд, очень подробная информация всё понятно.
Благодарю!
Есть такой кусок кода:
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#define PIN_0
#define PIN_1
#define PIN_2
#define PIN_3
#define PIN_4
#define PIN_5
#define PIN_6
#define PIN_7
#define PIN_8
#define PIN_9
#define PIN_10
#define PIN_11
#define PIN_12
#define PIN_13
#define PIN_14
#define PIN_15
#define PIN_16
#define PIN_17
#define PIN_18
#define PIN_19
#endif
И он рабочий. Я не могу понять его, ведь у #define нет второго аргумента! Кто объяснит?
Кода в данном куске нет, только макроопределения.
Подобного типа директивы #define могут использоваться с последующим использованием директив условной компиляции.
Например,
2
3
4
5
6
7
8
9
10
11
using namespace std;
#define PIN_0 // если закомментировать эту строчку, вывода не будет
int main()
{
#ifdef PIN_0
cout << "PIN_0";
#endif
cin.get();
return 0;
}
Конечно, константе PIN_0 никакое значение не присвоено, и если мы попробуем присвоить ее какой-то переменной, то получим ошибку.
Но подобный синтаксис вполне можно использовать в совокупности с директивами условной компиляции.
Добрый день! Не могу понять процессы компиляции и компоновки.
Из прочитанной информации запомнил следующее:
При директиве #Include < > препроцессор помещает в исходный файл содержимое файла библиотеки указанного в скобках. Дальше идет компиляция — перевод на машинный код и получается объектный файл. Потом компоновка — к объектному файлу добавляется код запуска и библиотечный код .
Не могу понять для чего на этапе компоновки добавляется код файла библиотеки если еще до компиляции код файла библиотеки был помещен в исходный код.
Подскажите пожалуйста.
В заголовочном файле содержатся прототипы функций (то, как их использовать), а исполняемый код для этих функций находится в соответствующих библиотеках
Спасибо за информацию.
Как работают макросы?
Есть такой кусок кода (выжимка из h файлов stm32):
(Проверяю в онлайн редакторе https://ideone.com/hehrYD)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <stdio.h>
#include <stdlib.h>
#define __IO volatile /*!< Defines 'read / write' permissions */
/**
* @brief General Purpose I/O
*/
typedef struct
{
__IO uint32_t CRL;
__IO uint32_t CRH;
__IO uint32_t IDR;
__IO uint32_t ODR;
__IO uint32_t BSRR;
__IO uint32_t BRR;
__IO uint32_t LCKR;
} GPIO_TypeDef;
#define PERIPH_BASE 0x40000000UL /*!< Peripheral base address in the alias region */
#define APB1PERIPH_BASE PERIPH_BASE
#define APB2PERIPH_BASE (PERIPH_BASE + 0x00010000UL)
#define GPIOA_BASE (APB2PERIPH_BASE + 0x00000800UL)
#define GPIOA ((GPIO_TypeDef *)GPIOA_BASE)
int main()
{
int i = GPIOA;
printf ("%x",i);
getchar();
return 0;
}
результат 40010800
Вопрос:
почему так работает
а так нет
или так тоже нет
подозреваю что в одном случае интерпретирует запись как указатель, а в других как унарную операцию умножения. но ПОЧЕМУ? как понять в каком случае и как будет происходить интепрпретация? как происходить "разворачивание" макроса?
Потому что препроцессор — сугубо текстовый процессор. Он может ТОЛЬКО заменять один текст другим. Для того, чтобы понять что и почему так не работает нужно включить в опции компилятора вывод препроцессированного текста (в Keil: Project -> Options for … -> вкладка Listing -> установить галочку чекбокса "C Preprocessor Listing"), вооружиться книжкой по Си, открыть раздел работы с указателями и внимательно смотреть.
Все понятно, но до сих пор я не могу понять для чего нужны директивы препроцессора. С #define и #include все понятно. А с остальными, что даёт использование остальных директив? Почему их нужно или не нужно использовать? В чем отличие функций написанные с помощью директив препроцессора или обычным способом?
Ну, например, директивы условной компиляции позволяют по-разному генерировать исполнимый код в зависимости от каких-то параметров.
Допустим, в зависимости от типа микроконтроллера, для которого пользователь генерирует свой проект, подключаются разные библиотеки с описаниями регистров.
Или в зависимости от версии аппаратной платформы необходимо скомпилировать тот или иной код программы.
То есть проверка условий в директивах препроцессора предусматривает проверку каких-то настраиваемых перед компиляцией константных значений.
Значит с помощью такой дерективы код будет работать на более низком уровне и от этого его лучшее качество, я правильно понял ???
Нет. Директивы препроцессора влияют на процесс компиляции. Например, можно создать исполнимый файл из одного и того же кода для разных микроконтроллеров, если тип микроконтроллера определить через #if