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

Си препроцессор представляет собой макро язык, который используется для преобразования программы до того как она будет скомпилирована. Причем сама программа может быть не обязательно на Си, она может быть на С++, Objective-C или даже на ассемблере. В общем препроцессор представляет собой примитивный как-бы функциональный язык, с помощью которого можно делать вполне интересные вещи.

Как работает препроцессор.

Для понимания работы препроцессора важно осознавать уровень абстракций с которыми он работает. Основным понятием в препроцессоре является токен (token) — это, грубо говоря последовательность символов, отделённая разделителями, похоже на идентификатор в Си, но значительно шире. В общем в препроцессоре есть только директивы, токены, строковые и числовые литералы и выражения, еще он понимает комментарии (просто их игнорирует). Упрощенно говоря, препроцессор работает с текстовыми строчками, умеет их склеивать, превращать в строковый литерал, выполнять макроподстановку, и подключать файлы.

Директивы препроцессора.

Директивы — это специальные команды, которые препроцессор распознаёт и выполняет. Все директивы начинаются со знака #. Если первый непробельный символ в строке это — #, то препроцессор будет пытаться распознать в ней свою директиву.
Существуют следующие директивы:
— Подключение файлов: #include, #include_next.
— Условная компиляция: #if, #ifdef, #ifndef, #else, #elif and #endif.
— Диагностика: #error, #warning, #line.
— Дополнительная информация компилятору: #pragma
— Макроопределения: #define

Подключение файлов.
Первая директива, которая всем встречается при изучении языка Си — это #include. Записывается так:
#include <имя_файла>
// или так
#include "имя_файла"

Встретив в исходнике эту директиву, препроцессор заменяет её на содержимое файла, имя которого указанно в параметре. Различие между первой и второй формой записи состоит в том, где в первую очередь, препроцессор будет искать указанный файл. В первом случае он сначала будет искать в каталогах с системными заголовками. Во втором — в том-же каталоге, где находится компилируемый исходник. Грубо говоря, при подключении системных/стандартных заголовков нужно имя файла писать в угловых скобках, а для своих — в кавычках.
Мало кто знает, но есть ещё одна директива для включения файлов — #include_next. Записывается она также как и обычный #include, но ее поведение несколько отличается. Дело в том, что препроцессор ищет подключаемые заголовки по многим путям, и бывает, что искомый файл есть сразу в нескольких каталогах. В случае применения директивы #include, он подключает первый попавшийся файл с совпавшим именем. В случае #include_next — будет подключен первый файл с совпавшим именем, который еще не включался в эту единицу трансляции, то есть следующий еще не подключенный. Причем применять #include_next можно только в этих самых заголовках с совпадающими именами, применённая в.с файле эта директива ведёт себя как обычный #include. Таким образом, если в каждом из заголовков с одинаковыми именами применить #include_next, то конечном итоге, они все будут подключены.
Ещё одна интересная особенность директивы #include то, что в ней тоже выполняется макроподстановка. То есть, параметра в ней можно использовать любой макрос, который развернётся в имя файла в одной из двух допустимых форм(в кавычках или в угловых скобках). Например:

#define TARGET avr
...
#define FNAME_CONCAT(First, Second) <First/**/Second>
#include FNAME_CONCAT(TARGET,/io.h)

В данном случае макрос в директиве include развернётся в #include <avr/io.h>. Для склеивания частей пути к файлу использован трюк с пустым комментарием. Таким образом можно, например, выбирать каталог для подключения системно-зависимых заголовков. Хотя пример несколько надуманный и выбирать каталоги для системно-зависимых заголовков лучше в системе сборки проекта через соответствующие ключи компилятору. Еще эту технику можно использовать для того, чтобы подключить в определенном месте заголовок, имя которого определено где-то далеко, например в недрах какой-то библиотеки.

Условная компиляция
Применяется, когда в зависимости от значения различных макросов, нужно компилировать, или нет, тот или иной кусок кода, или установить другие макросы.

#if <i>условие</i>
// код компилируемый если условие истинно.
#elif <i>условие2</i>
// код компилируемый если условие2 истинно.
#else
// код компилируемый если ни одно из условий выше не выполнилось
#endif

Где условие — это выражение препроцессора. Это может быть любая комбинация макросов, условий и целочисленных литералов, которая в результате макроподстановки превратится в выражение состоящее только из целочисленных литералов, арифметических операций и логических операторов. Так-же здесь ещё можно использовать единственный «макрооператор» — defined — он превращается в 1, если его операнд определён, и 0 — если нет.

#if defined(__AVR__) // если компилятор avr-gcc
// код для avr-gcc
#elif defined(__ICCAVR__) // если компилятор IAR for avr
// код для IAR
#else // непонятно какой компилятор
// прерываем компиляцию с сообщением об ошибке
#error "Неподдерживаемый компилятор"
#endif

__AVR__ и __ICCAVR__ — это специальные предопределённый макросы, позволяющие определить используемый компилятор. Соответственно для каждого компилятора существует предопределённый макрос, который позволяет его однозначно идентифицировать.
Как уже говорилось, препроцессор работает на уровне отдельных токенов — текстовых строчек, их значение препроцессору безразлично, и он ничего не знает о правилах и грамматике целевого языка. Поэтому в директивах условной компиляции нельзя использовать никакие конструкции языка Си. Например:

struct A{
int foo;	
char array[ARR_SIZE];
...
};
// попытка проверить, влезет, ли структура в EEPROM
#if sizeof(A) > EEPROM_SIZE
#error "структура A не помещается в EEPROM"
#endif
…
int foo;
// попытка проверить определена, ли переменная foo
#if defined(foo)
…
#endif

В обоих приведённых примерах условия будут всегда ложны и содержимое #if блоков не выполнится. Препроцессор не знает ничего, ни о структурах и их размере, ни о переменных — они-ж не макросы. По этому в первом случае нужно использовать static_assert, реализованный средствами самого Си. А вот во втором случае можно извернутся так:

int foo;
#define foo foo  // <-----------------
...
// теперь сработает
#if defined(foo)
…
#endif

Условия могут быть сложными и содержать в себе макросы, которые будут полностью развёрнуты перед вычислением условия:

// возвращает 1 если x - степень двойки
#define IS_POWER_OF_2(x) ((x) & ((x)-1) == 0)
…
#define BUFFER_SIZE 16
…
#if IS_POWER_OF_2(BUFFER_SIZE) && defined(OPTIMIZE_FOR_POWER_OF_2)
...

В данном примере блок #if выполнится если макрос BUFFER_SIZE имеет значение кратное степени двойки и если определен макрос OPTIMIZE_FOR_POWER_OF_2. Конструкция IS_POWER_OF_2(BUFFER_SIZE) после макроподстановки развернется в выражение ((16) & (16)-1 == 0), которое препроцессор легко вычислит.
Для конструкции типа #if defined есть сокращенная форма: #ifdef. Она во всём эквивалентна полной форме, за исключением того, что в сокращенной форме нельзя комбинировать несколько условий.
Также директивы условной компиляции часто используются для предотвращения повторного включения заголовочных файлов (include guard):

#ifndef HEADER_FILE_NAME_H
#define HEADER_FILE_NAME_H
// содержимое файла
...
#endif

Эта конструкция гарантирует, что все определения из заголовка будут включены только один раз в единицу трансляции.
Диагностика.
В предыдущих примерах мы уже встретились с одной диагностической директивой — #error. Назначение её предельно просто — остановить компиляцию с сообщением об ошибке, указанном после директивы. Её можно использовать совместно с директивами условной компиляции для того, чтоб убедиться установлен ли какой-то важный макрос и, что он имеет правильное значение.
Также существует директива #warning, аналогична #error, но не прерывает компиляцию, а выдаёт предупреждение.
Директива #line служит для задания номеров строк и имени файла, показываемых в сообщениях об ошибках и возвращаемые специальными макросами __LINE__ и __FILE__. Например:

#line 1000 "my_file_name"
#error "Hello there!!!"

При этом в сообщениях об ошибках мы увидим следущее:

file		line	Message
my_file_name	1000	error: #error "Hello there!!!"

Надо учитывать, что такая конструкция собьёт столку любую IDE (и человека тоже) и найти место ошибки будет очень не просто. Однако этот трюк можно использовать, чтоб указать на ошибку, возникшую где-то далеко от места, где мы ее проверяем, например на какой-то важный макрос, определённый в другом файле и имеющий не правильное значение. Надо только точно знать где он расположен.

#pragma
Эта директива используется в основном для передачи компилятору различных нестандартных опций, таких как уровни оптимизаций для отдельных частей программы, выравнивания структур, параметров компоновщика и много чего ещё. У каждого компилятора свой набор директив pragma, но есть и стандартные, поддерживаемые многими компиляторами. Например, директива #pragma once . Это замена для include guard, встретив в заголовке эту директиву, препроцессор предотвратит повторное включение этого заголовка в обрабатываемую единицу трансляции.

Макроопределения
Теперь переходим к интересному, собственно к макросам. существуют два типа макросов: макрос-объект(object-like macro) и макрос-функция(function-like macro), оба типа объявляются с помощью директивы #define. Рассмотрим сначала макросы-объекты. Объявляются они как:
#define ИМЯ_МАКРОСА [замещающий текст]
Всё, что идёт после имя макроса до конца строки является замещающим текстом.

#define BUFFER_SIZE 32
#define PORTA (*(volatile uint8_t *)0x1B)

При дальнейшей обработке файла, если препроцессор находит имя макроса, он заменяет его на соответствующий замещающий текст — это называется макроподстановка. Если в замещающем тексте макроса встречаются имена других макросов, препроцессор выполнит макроподстановку для каждого из них, и так далее, пока не будут развёрнуты все известные на данный момент макросы.

#define BUFFER_SIZE 32
#define EXTRA_BUFFER     (BUFFER_SIZE + 10)
//                    ^ - здесь пробел обязателен
#define DOUBLE_BUFFER EXTRA_BUFFER * 2
…
char buffer[DOUBLE_BUFFER];

Когда препроцессор будет обрaбатывать строчку:
char buffer[DOUBLE_BUFFER];
Сначала будет выполнена первая макроподстановка и токен DOUBLE_BUFFER будет заменен на EXTRA_BUFFER * 2. Тут-же будет выполнена вторая макроподстановка и токен EXTRA_BUFFER заменется на (BUFFER_SIZE +10), потом BUFFER_SIZE заменется на 32. В результате вся строчка после препроцессинга будет выглядеть так:
char buffer[(32 + 10) * 2];

Здесь становится понятно, зачем были нужны скобки в макросе EXTRA_BUFFER, без них результирующее выражение получилось бы таким:
char buffer[32 + 10 * 2];

А это явно не то, что мы хотели получить. Отсюда правило:
Если макрос содержит какое-то выражение, то оно обязательно должно быть заключено в скобки, иначе могут происходить всякие неочевидные вещи.
Также важно понимать, что препроцессор сам ничего не вычисляет (кроме как в условных директивах #if), он просто склеивает текстовые строчки.
А что будет, если какой-то макрос будет ссылаться сам на себя, непосредственно, и косвенно через другие макросы? Ничего не будет, рекурсии не получится, как только препроцессор просечет рекурсию, он прекратит макроподстановку макроса её вызвавшего и оставит его как есть. Например:
int flags;
#define flags flags

При этом будет определён и символ препроцессора flags и переменная flags. такую особенность часто используют для того, чтобы иметь возможность проверить наличие переменной(или любого другого идентификатора) с помощью директив условной компиляции #ifdef/#else/#endif:
// если флаги определены
#ifdef flags
// работаем с флагами
flags |= flag1;
…
#else
// если нет - и без них обойдёмся
#warning "'flags' is not defined"
DoSomethisngWithoutFlags();
#endif

Хотя я бы не рекомендовал использовать такой приём без крайней необходимости, так как он сильно затрудняет понимание программы и чреват ошибками, поскольку мы по сути пишем две версии программы в одном наборе исходников со всеми вытекающими последствиями. Ведь есть-же системы контроля версий!

Предопределённые макросы
У каждого компилятора есть множество предопределённых макросов, есть стандартные — общие для всех: gcc.gnu.org/onlinedocs/cpp/Standard-Predefined-Macros.html#Standard-Predefined-Macros
Есть специфичные для каждого отдельного компилятора, например у gcc:
gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.html#Common-Predefined-Macros
И даже для каждой поддерживаемой платформы, например для avr-gcc, список всех предопределённых макросов(кроме контекстно зависимых, таких как __LINE__ и т.д) можно получить набрав в командной строке:
avr-gcc.exe -mmcu=atmega16 -dM -E - < /nul

Соответственно, вместо atmega16 писать интересующий контроллер.
В других компиляторах предопределённые макросы ищутся в соответствующей документации.
Все эти макросы могут использоваться для определения платформы, для которой компилируется программа, используемого языка (Си, Си++ или ассемблер) и различных особенностей целевой архитектуры.
Также есть макросы предназначенные в основном для отладки: __FILE__, __LINE__ и __FUNCTION__. __FILE__ разворачивается в строковый литерал, содержащий имя обрабатываемого файла. __LINE__ — целочисленный литерал означающий номер текущей строки. __FUNCTION__ — имя текущей функции. Надо заметить, что макрос __FUNCTION__ разворачивается всё-таки не препроцессором а компилятором — препроцессор ничего не знает о функциях в языке Си. Также надо учитывать, что значения __LINE__ и __FILE__ могут изменяться с помощью директивы #line.
Типичное использование макросов __LINE__, __FILE__ и __FUNCTION__:
#define LOCATION __LINE__ , __FILE__, __FUNCTION__
...
void MyError(int line, char * file, char * function, char * message)
{
fprintf(stderr, "Error in file %s in function %s at line %d: %s", 
file, function, line, message);
}
…
MyError(LOCATION, "Shit happened");

При этом вызов функции MyError превратится во что-то такое:
MyError(100, "main.c", "my_function", "Shit happened");


Макросы-функции
Второй вид макросов — это макро-функции (function-like macros). Определяются они с помощью той-же директивы #define, после которой (сразу без пробелов) в круглых скобках идёт список разделённых запятыми аргументов:
#define SQR(x) (x * x)
…
int a = SQR(b);

Макрос SQR предназначен вычислять квадрат переданного ему выражения, в приведённом примере SQR(b) развернётся в (b * b). Вроде-бы нормально, но если этому макросу передать более сложное выражение
int a = SQR(b + c)
,
то он развернётся совсем не в то, что нужно:
int a = (b + c * b + c);

Очевидно, что умножение выполнится первым и это у нас уже далеко не квадрат.
Поэтому все аргументы макросов используемые в математических и не только выражениях надо обязательно заключать в скобки:
#define SQR(x) ((x) * (x))
int a = SQR(b + c); // - - > ((b + c)*(b + c))

Однако и этот вариант не свободен от недостатков, например:
int a = SQR(b++);

превратится в:
int a = ((b++) * (b++));

Переменная b будет инкрементирована два раза. И у этого недостатка есть решения гибкие и не очень, стандартные и нет, но о них говорить не будем. В данном примере гораздо лучше применить встраиваемую (inline) функцию, она свободна от недостатков макросов:
inline int sqr(int x){return x * x;}


У макросов-функций есть интересная особенность — макроподстановка в них выполняется два раза. Первый раз — для каждого из параметров до того, как они будут подставлены в тело макроса. Второй раз — для всего тела макроса после подстановки в него параметров. В большинстве случаев это не имеет особого значения. Подробнее об этом можно прочитать здесь:
gcc.gnu.org/onlinedocs/cpp/Argument-Prescan.html#Argument-Prescan

В макро-функциях можно использовать два специальных макро-оператора: # и ##. Первый превращает свой операнд в строковый литерал:
#define PRINT_VAR(VAR_NAME, FORMAT) printf("%s = %" #FORMAT, #VAR_NAME, VAR_NAME)
…
int my_var = 10;
PRINT_VAR(my_var,d);

Вызов PRINT_VAR в данном случае превратится в
printf("%s = %" "d", "my_var", my_var);

При этом будет напечатана строка: my_var = 10. Здесь для склеивания форматной строки использован тот факт, что две строки разделённые лишь пробельными символами компилятор считает одной строкой: "%s = %" «d».
Макро-оператор ## склеивает два токена в один токен, для которого после будет выполнена макроподстановка:
#define LED_PIN B, 4
#define SET_PIN(ARGS) __SET_PIN(ARGS)
#define __SET_PIN(PORT_LETTER, PIN) PORT ## PORT_LETTER |= (1 << PIN)
…
SET_PIN(LED_PIN);
// - - > 
// PORTB |= (1 << 4);
// PORTB - тоже развернётся, если это макрос

Применять эти макро-операторы можно только к параметрам макросов. Причем для параметров к которым они применены макроподстановка будет применена только один раз — для полученного результата. То есть параметр PORT_LETTER не будет отдельно сканироваться на наличие в нем макросов. Почему макрос SET_PIN состоит из двух уровней объясняется ниже.
Теперь, допустим, нам нужен макрос, который склеивает идентификатор из двух кусков:
#define CONCAT(FIRST, SECOND) FIRST ## SECOND
...
CONCAT(PORT, B) |= (1 << LED_PIN); 
// сработает
// PORTB |= (1 << 4); 
…
#define OUT_REG PORT
#define PORT_LETTER B
CONCAT(OUT_REG , PORT_LETTER ) |= (1 << LED_PIN);
// ошибка компиляции
// - - > OUT_REGPORT_LETTER |= (1 << 4);

Если параметра этого макроса непосредственно, те токены, что нам нужно склеить, как в примере выше, то всё сработает как надо. Если-же это макросы, которые сначала нужно раскрыть, то придется вводить еще один вспомогательный макрос, который сначала развернёт параметры и передаст их следующему макросу:
#define CONCAT__(FIRST, SECOND) FIRST ## SECOND
#define CONCAT(FIRST, SECOND) CONCAT__(FIRST, SECOND)
...
CONCAT(PORT, B) |= (1 << LED_PIN); 
// сработает
// PORTB |= (1 << 4); 
…
#define OUT_REG PORT
#define PORT_LETTER B
CONCAT(OUT_REG , PORT_LETTER ) |= (1 << LED_PIN);
// так теперь тоже сработает
// PORTB |= (1 << 4); 

Из-за того, что для параметров, для которых применена конкатенация, не производится макроподстановка, в препроцессорном метапрограммировании часто приходится применять такие двухуровневые макросы: один — для развёртывания параметров, второй — делает непосредственную работу.

Макро-функции можно передать имя другой макро-функции в качестве параметра и, соответственно, вызвать её:
#define FOO(MACRO, ARG) MACRO(ARG, SECOND_ARG)
#define BAR(FIRST, SECOND) /*то-то делаем*/
…
FOO(BAR, MY_ARG);


Практический пример препроцессорного метапрограммирования

В качестве примера рассмотрим генерацию таблицы для вычисления контрольной суммы CRC16. Функция для вычисления CRC16 для каждого байта выглядит так:

uint16_t Crc16Update(uint8_t newchar, uint16_t crcval)
{
    return (crcval >> 8) ^ сrcTable[(crcval ^ newchar) & 0x00ff];
}

Где newchar — очередной байт сообщения для которого вычисляем CRC,
crcval — предыдущее значение CRC.
сrcTable — таблица из 256 значений, которую нам надо сгенерировать.
Функция возвращает новое значение контрольной суммы.

Первоначальная идея была и вовсе вычислять CRC16 от строкового литерала с помощью препроцессора, чтобы можно было реализоват «switch» по CRC16 от строки, с удобочитаемыми метками. Но только на препроцессоре это сделать не получилось из-за степенной сложности генерируемых выражений — компилятору банально не хватает памяти, чтоб посчитать таким образом CRC16 для двух символов. На шаблонах С++ это можно сделать без проблем.


Елементы таблицы сrcTable можно вычислить с помощью такой функции:

unsigned short CrcTable(unsigned short v, unsigned short polynom)
{
    for (unsigned i = 8; i--; )
        v = v & 1 ? (v >> 1) ^ polynom : v >> 1;
    return v;
}

Где v — индекс в таблице,
polynom — полином контрольной суммы, в данном примере будем использовать значение 0x8408, соответствующее стандарту CRC-CCITT.

Теперь нужно этот алгоритм реализовать с помощью препроцессора. Как быть с циклом? В препроцессоре нет ни циклов ни рекурсии. Прийдётся цикл развернуть вручную:
#define CRC_TABLE_1(VALUE, POLYNOM) ((VALUE) & 1 ? ((VALUE) >> 1) ^ (POLYNOM) : (VALUE) >> 1)
#define CRC_TABLE_2(VALUE, POLYNOM) (CRC_TABLE_1(VALUE, POLYNOM) & 1 ? (CRC_TABLE_1(VALUE, POLYNOM) >> 1) ^ (POLYNOM) : CRC_TABLE_1(VALUE, POLYNOM) >> 1)
#define CRC_TABLE_3(VALUE, POLYNOM) (CRC_TABLE_2(VALUE, POLYNOM) & 1 ? (CRC_TABLE_2(VALUE, POLYNOM) >> 1) ^ (POLYNOM) : CRC_TABLE_2(VALUE, POLYNOM) >> 1)
#define CRC_TABLE_4(VALUE, POLYNOM) (CRC_TABLE_3(VALUE, POLYNOM) & 1 ? (CRC_TABLE_3(VALUE, POLYNOM) >> 1) ^ (POLYNOM) : CRC_TABLE_3(VALUE, POLYNOM) >> 1)
#define CRC_TABLE_5(VALUE, POLYNOM) (CRC_TABLE_4(VALUE, POLYNOM) & 1 ? (CRC_TABLE_4(VALUE, POLYNOM) >> 1) ^ (POLYNOM) : CRC_TABLE_4(VALUE, POLYNOM) >> 1)
#define CRC_TABLE_6(VALUE, POLYNOM) (CRC_TABLE_5(VALUE, POLYNOM) & 1 ? (CRC_TABLE_5(VALUE, POLYNOM) >> 1) ^ (POLYNOM) : CRC_TABLE_5(VALUE, POLYNOM) >> 1)
#define CRC_TABLE_7(VALUE, POLYNOM) (CRC_TABLE_6(VALUE, POLYNOM) & 1 ? (CRC_TABLE_6(VALUE, POLYNOM) >> 1) ^ (POLYNOM) : CRC_TABLE_6(VALUE, POLYNOM) >> 1)
#define CRC_TABLE_8(VALUE, POLYNOM) (CRC_TABLE_7(VALUE, POLYNOM) & 1 ? (CRC_TABLE_7(VALUE, POLYNOM) >> 1) ^ (POLYNOM) : CRC_TABLE_7(VALUE, POLYNOM) >> 1)

Теперь, вызывая макрос CRC_TABLE_8 мы получаем константное выражение для одного элемента таблицы. Выражение это, кстати, очень длинное порядка 200-400 тысяч символов! Это происходит потому, что каждый(кроме первого) макрос CRC_TABLE_x вызывает 3 макроса более нижнего уровня, а ведь препроцессор сам выражения не вычисляет, оставляя это компилятору. И получается в результате длинна такого выражения порядка 3 в восьмой степени помножить на длинны выражения низшего уровня. Но ничего, это компилятор еще прожевывает. Теперь нужно сгенерировать саму таблицу:
#define CRC_POLYNOM 0x8408
uint16_t crcTable[]=
{
CRC_TABLE_8(0, CRC_POLYNOM),
CRC_TABLE_8(1, CRC_POLYNOM),
CRC_TABLE_8(2, CRC_POLYNOM),
…
// итак до 256 значений
CRC_TABLE_8(255, CRC_POLYNOM)
};

Можно, конечно оставить и так, но есть решение получше, называется — библиотека Boost preprocessor. В ней имеется много всяких полезняшек, в частности есть макрос BOOST_PP_REPEAT, который повторяет заданное количество раз макрос, переданный ему в качестве параметра. С использованием BOOST_PP_REPEAT геерацию таблицы можно написать так:
#define CRC_TABLE_ITEM(LEVEL, INDEX, DATA) CRC_TABLE_8(INDEX, DATA),
int crcTable[] =
{
    BOOST_PP_REPEAT(255, CRC_TABLE_ITEM, 0x8408)
};

Выглядит уже вполне неплохо. Макрос, который будет повторяться в BOOST_PP_REPEAT, должен иметь три параметра. Первый уровень вложенности повторения, если мы будем использовать вложенные повторения, мы его не используем. Второй — счётчик, текущая итерация — индекс в нашей таблице. Третий — дополнительный параметр, мы в нем передаём полином контрольной суммы.

Как-же работает BOOST_PP_REPEAT, если в перпроцессоре нет ни циклов, ни рекурсии. Очень просто — определено 256 макросов с именами типа BOOST_PP_REPEAT_x, где х — номер итерации, которые вызывают друг друга по цепочке. В макросе BOOST_PP_REPEAT склеивается имя макроса этой цепочки из токена BOOST_PP_REPEAT_ и количества требуемых повторений. Это несколько упрощенное объяснение, в реальности там чуть сложнее, но основной принцип такой.

Ссылки

gcc.gnu.org/onlinedocs/cpp/
www.keithschwarz.com/cs106l/spring2009/handouts/080_Preprocessor_2.pdf
www.boost.org/doc/libs/1_46_1/libs/preprocessor/doc/index.html

  • +19
  • 21 июня 2011, 23:07
  • neiver
  • 1
Файлы в топике: CRC_meta.zip

Комментарии (56)

RSS свернуть / развернуть
это просто охрененная статья!
0
спасибо, многое вспомнил, ещёбы сделали статью для начинающих программировать под МК, в плане того чтобы разъяснить к примеру строчку CONCAT(PORT, B) |= (1 << LED_PIN), в общем рассказать прицип как и почему оно работает)
0
  • avatar
  • pkm
  • 22 июня 2011, 00:07
В первом случае он сначала будет искать в каталогах с системными заголовками. Во втором — в том-же каталоге, где находится компилируемый исходник
Наоборот. Поменяй в примере кода первую и последнюю строки местами :)
Для склеивания частей пути к файлу использован трюк с пустым комментарием.
А почему так? Вроде ж был для этого специальный синтаксис, что-то вроде ##?
int flags;
#define flags flags

Вот эту часть я не понял. Если закомментировать или удалить первую строчку — последущий где-то в коде ifdef flags выдаст false? Если да, то как оно работает? А если нет, то зачем определять #define flags flags, а не, например, #define flags 1?
Теперь нужно сгенерировать саму таблицу:
#define CRC_POLYNOM 0x8408
Опять баг вроде, дефайн должен быть в блоке кода, а не тексте.
Алсо, выпили html теги из примеров кода. Они там игнорируются.
0
  • avatar
  • Vga
  • 22 июня 2011, 00:50
не #define flags 1 потмоу что иначе вместо имени переменной будет подставляться 1. а так получается бесполезная замена, но с точки зрения препроцессора макрос определен
0
Хм, действительно.
0
%% Для склеивания частей пути к файлу использован трюк с пустым комментарием. %%
А почему так? Вроде ж был для этого специальный синтаксис, что-то вроде ##?
Оператор ## можно применять только к параметрам макрофункций. По хорошему в этом примере нужно было применить двухуровневый макрос типа CONCAT, потому, что склеивание с помощью комментария может не удалить пробелы между токенами в некоторых случаях.
0
отлично! с удовольствием ваши статьи читаю, хотя и не всегда до конца понимаю (C++)
0
есть еще такая штука как m4. в каких случаях может оказаться целесообразно ее применять?
0
Написано:
#ifdef HEADER_FILE_NAME_H
#define HEADER_FILE_NAME_H
// содержимое файла
...
#endif

Следует читать:
#ifndef HEADER_FILE_NAME_H
#define HEADER_FILE_NAME_H
// содержимое файла
...
#endif
+1
Спасибо за замечание
0
спасибо, хорошая статья. Я её даже в pdf замутил, вот если кому надо
cs10667.userapi.com/u38358400/doc/cc4194cd22c6/Preprotsessor_C.pdf
0
… у меня чёта не получается скачать :(
0
хм, может так ifolder.ru/24307238
0
… оо, процуэ, благодарю
0
… отличная статья
0
… хорошобы еще почитать статью о том как замутить откомпилированную библиотеку, типа.а файл или чтото в этом роде, тоесть чтоб можно было файл раздавать а содержимое не раскрывать по разным причинам
0
Библиотека GCC — это просто архив с.о файлами. Архивируешь нужные объектные файлы утилиткой ar вместо линковки (это называется «линковка статической библиотеки», хе-хе) и библиотека готова.
Вот только в этих ваших МК от библиотек толку не сильно много. Либо она будет под 2-3 МК (мега 16 и 16А, например), либо в ней нельзя работать с SFR. Ну и набор команд разных серий МК отличается.
Ну и опять же, код всегда можно дизассемблировать. Раскурить несколько килобайт объектного кода после дизассемблирования — дело пары дней. Тем более, что в объектниках есть информация о символах.
0
Вопрос может не совсем в тему, но про Си
В примерах для STM32, в файле system_stm32f10x.h
есть ф-ция extern void SystemInit(void)
может кто-нить прокомментировать что значит extern
Еще что странно ф-ция нигде в программе не вызывается, но тем не менее идет первой
для этого как раз extern?
0
Это значит, что функция реализована где-то вовне и компилеру не следует пытаться найти ее тело. Хотя он вроде и так не ругается на функции, которые только объявлены, но не реализованы, так что не уверен, какой смысл оно имеет применительно к ним. А вот к переменным — имеет. Благодаря строчке вида extern byte status компилер будет знать о переменной status и позволит к ней обращаться, но не станет выделять под нее место, полагая что оно выделено где-то еще.
0
Так нигде в хидерах ф-ции никогда не объявляются. А тут почему-то именно для этой странной ф-ции
0
функция SystemInit вызывается из стартового кода перед функцией main для того, чтобы инициализировать переферию. В стартовом коде(он может быть как на Си, так и на ассемблере) имеется объявление этой функции, скорее всего не в каком-то заголовке, а прям в исходнике. extern нужен не для компилятора — функции и так по умолчанию extern, если не объявлены static, а для человека, чтоб было сразу видно, что функция будет вызываться из другого модуля(единицы трансляции).
0
extern нужен не для компилятора — функции и так по умолчанию extern, если не объявлены static, а для человека, чтоб было сразу видно, что функция будет вызываться из другого модуля(единицы трансляции).
странно, у меня gcc ругался на отсутствие тела функции, если небыло прописано extern…
0
А вот насчет макросов. Опять же в исходниках для STM32 наткнулся на такой макрос #define assert_param(expr). Из описания я понял что он проверяет подставляемые параметры и возращает место ошибки. но подставляется он обычно в конце исходников и тесктах я его не встречал. Может кто-нить пояснить его предназначение?
0
Из stm32f10x_gpio.c:
uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx)
{
  /* Check the parameters */
  assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
  
  return ((uint16_t)GPIOx->IDR);
}

Этот макрос генерирует код, который в случае, если переданное ему выражение ложно прерывает прерывает выполнение программы вызвав функцию assartion_failed, управление из которой никогда не вернётся.
IS_GPIO_ALL_PERIPH щпределен так:
#define IS_GPIO_ALL_PERIPH(PERIPH) (((PERIPH) == GPIOA) || \
                                    ((PERIPH) == GPIOB) || \
                                    ((PERIPH) == GPIOC) || \
                                    ((PERIPH) == GPIOD) || \
                                    ((PERIPH) == GPIOE) || \
                                    ((PERIPH) == GPIOF) || \
                                    ((PERIPH) == GPIOG))

В режиме отладки assert_param(IS_GPIO_ALL_PERIPH(GPIOx)); развернется в что-то такое:
if((((PERIPH) == GPIOA) || \
                                  ((PERIPH) == GPIOB) || \
                                  ((PERIPH) == GPIOC) || \
                                  ((PERIPH) == GPIOD) || \
                                  ((PERIPH) == GPIOE) || \
                                  ((PERIPH) == GPIOF) || \
                                  ((PERIPH) == GPIOG)) == 0)
{
    assert_failed(__FILE__, __FUNCTION__, __LINE__);
}

Без режима отладки этот макрос не будет генерировать никокого кода.
0
Большое спасибо автору! Очень познавательная и толковая статья!
0
Спасибо за интересную и полезную статью!
Может подскажите, есть ли правила хорошего тона касательно того, какие headers в какой последовательности подключать (имею ввиду сначала свои, потом системные или наоборот)?
0
Лучше сначала писать системные заголовки, причем во всех файлах проекта в одинаковом порядке. Обработав системные заголовки, компилятор может сохранить своё состояние на диск и при компиляции следующего файла, который включает такие-же заголовки, вместо того, чтобы обрабатывать все заголовки заново, просто восстанавливает своё состояние из файла. Механизм называется «Precompiled headers». Он позволяет существенно ускорить компиляцию больших проектов. А для проектов из двух-трёх файлов не важно в каком порядке подключать заголовки если, конечно между ними нет зависимостей.
0
Ok. Спасибо еще раз!
0
чето меня переклинило не могу развернуть макрос

#define ADR 8
#define _NAME c3_1_AI

#define NAME _NAME##ADR — так не выходит

нужно чтобы вышло: c3_1_AI8
но чето никак
0
Это по тому, что во-первых оператор ## можно применять только к аргументам макро-функции, во-вторых для этих аргументов макроподстановка не выполняется. Здесь нужен двухуровневый макрос:
#define CONCAT__(FIRST, SECOND) FIRST ## SECOND
#define CONCAT(FIRST, SECOND) CONCAT__(FIRST, SECOND)

#define ADR 8
#define _NAME c3_1_AI
#define NAME CONCAT(_NAME,ADR)
Собственно, в статье в разделе «Макросы-функции» про это написано.
0
собсна смотрю в книгу вижу фигу…
0
спасибо все вроде получается!
но как то все очень мутно…
0
Это Спарта! препроцессор Си.
0
Стоит попробовать вот так:

#define CONCAT(a,b)    a##b
#define NAME    CONCAT(_NAME,ADR)


Там есть приколы с порядком подстановки значений. За давностью детали забыл, но так как я написал выше должно работать.
0
Неа, тут нужен именно двухуровневый макрос. С одноуровневым значение NAME будет — _NAMEADR вместо c3_1_AI8.
0
Да, точно. Написал, а потом вспомнил, как сам боролся с похожей проблемой много лет назад.
0
эээ SET_PIN ваш в статье не должен работать!!! Вы что код не проверили или у вас действительно работает?

#define LED_PIN B, 4
#define SET_PIN(PORT_LETTER, PIN) PORT ## PORT_LETTER |= (1 << PIN)
…
SET_PIN(LED_PIN);
// - - > 
// PORTB |= (1 << 4);
// PORTB - тоже развернётся, если это макрос

Смотрите почему: препроцессор первым делом всегда разворачивает макросы в которых заюзано ## и #(это написано и в документации и вы сами можете попробовать посмотреть выброс), по этому при первом проходе он развернёт именно SET_PIN
и получится

PORTLED_PIN |= (1 << О_о);
а пин ему и подавно не откуда взять! Ну а второй проход уж будет бессмыслен, потому что LED_PIN уже нету в тексте программы, а тот кусок, который в PORTLED_PIN уже намертво пределан и препроцессором не рассматривается.
0
Да вы правы, я пропустил здесь второй уровень макроса, для того чтоб развернуть LED_PIN. Так работает:
#define SET_PIN(ARGS) __SET_PIN(ARGS)
#define __SET_PIN(PORT_LETTER, PIN) PORT ## PORT_LETTER |= (1 << PIN)

Спасибо за найденную ошибку.
0
ага)) я точно так же это реализовал=) не за что)
0
статья безусловно полезная, но только вот
#define IS_POWER_OF_2(x) ((x) & (x)-1 == 0)
надо писать как
#define IS_POWER_OF_2(x) ( ( (x)&( (x)-1 ) ) == 0)
из вики en.wikipedia.org/wiki/Power_of_two
positive x is a power of two if (x & (x − 1)) equals to zero.
При этом еще и учитывать что корректный результат макрос выдаст при x>1
0
Ага. Вполне справедливое замечание. Поправил.
0
Всем привет. Есть устройство со светодиодами, каждый из которых должен загораться при определённом пороге, с которым сравнивается значение из ADC. Иногда пороги нужно менять, и для того, чтобы не считать каждый раз для каждого входного напряжения какое значение будет на ADC, при этом учитывая ещё и входной делитель — хочу оставить это занятие препроцессору.
Код:
#define Uin 5 /*Входное напряжение*/
#define Rver 10 /*Сопротивление верхнего резистора в делители кОм*/
#define Rnij 10 /*Сопротивление нижнего резистора в делители кОм*/
#define LED1 0.625 /*Порог 1 светодиода*/
#define LED2 1.25 /*Порог 2 светодиода*/
#define LED3 1.875 /*Порог 3 светодиода*/
#define LED4 2.5 /*Порог 4 светодиода*/
#define LED5 3.125 /*Порог 5 светодиода*/
#define LED6 3.75 /*Порог 6 светодиода*/
#define LED7 4.375 /*Порог 7 светодиода*/
#define LED8 4.9 /*Порог 8 светодиода*/

#define ADConLED(LED) ( (LED*256)/Uin*Rnij/(Rnij+Rver) )

Теперь, если:

a=ADConLED(LED1);

Всё работает отлично, препроцессор всё считает, и в переменную а заносится нужное значение.
Теперь я могу сравнить текущее значение на ADC с порогом, значение которого в переменой а. Всё будет работать.

if(i<а){OutFoeLED=0;}

Но ведь препроцессор, считая по формуле, получает число, например 16, которое записывается в ПЗУ затем, при работе микроконтроллера из ПЗУ данное число запишется в оперативную память по адресу а.
Значит в if(i<а){OutFoeLED=0;} вместо а я могу написать ADConLED(LED1) и получить в итоге
if(i<ADConLED(LED1)){OutFoeLED=0;}, но такая инструкция не работает, точнее она работает, но препроцессор вместо ADConLED(LED1) подставляет не 16, как я жду, а подставляет формулу ((LED*256)/Uin*Rnij/(Rnij+Rver)) При этом всё компилируется, но так как переменные из формулы с плавающей запятой, такая подстановка приводит к тому, что у микроконтроллера заполняется 400% памяти, (вместо 70%, как при работе через промежуточную переменную).

Мне интересно, как можно в данном примере избавиться от промежуточной переменой, чтобы не тратить на неё оперативную память.
Спасибо.
0
Я не супер програмист. Но операция деления отжырает в аврок очень много памяти.
В одном из своих проектов, где нужно было использовать похожую формулу, я нагло захардкодил уже вищитаное вручную значения. Чем секономил уйму памяти.
Но повторюсь, я не супер программист и думаю вашей задаче есть адекватное решения. Может стоит переделать формулу, чтоб обойтись без операции деления?
0
Делит не микроконтроллер, а препроцессор(то есть компьютер).
В общем мне нужны константы, и для того, чтобы мне их не считать каждый раз, я хочу заставить считать их компилятор (точнее препроцессор). У меня это получается для инициализации переменной, но как константа это не работает.
0
Препроцессор ничего не считает, считает компилятор. А чтобы работало — тебе нужен дополнительный макрос для разворачивания параметров. Почитай то место, где расскзывается про макрос конкатенации.
0
В данном случае формула константная и посчитается при компиляции.
Что до проблем с кодом — это из-за особенностей раскрытия макросов. Ответ нужно искать в районе макроса конкатенации — который состоит из двух макросов.
0
Так и не смог разобраться со склейкой, и на сколько я понял, в моём случае этого не требуется (возможно я ошибаюсь), ведь мне не нужно собрать макрос из нескольких кусков. То, что нужно для макроса уже есть, ничего разворачивать не нужно. Более того, так как для присвоения переменной всё работает не увеличивая занимаемую память — значит макрос работает нормально. Пока пользуюсь промежуточной переменной и похоже что так всё и останется…

#define ADConLED(LED) ( (LED*256)/Uin*Rnij/(Rnij+Rver) ) 
a=ADConLED(LED1);
if(i<а){OutFoeLED=0;}

Работает как нужно. А так

if(i<ADConLED(LED1)){OutFoeLED=0;}

уже нет
0
А кой тип имеют «а» «i»? Это важно.
Подозреваю, что они целочисленные и происходит следующее

#define ADConLED(LED) ( (LED*256)/Uin*Rnij/(Rnij+Rver) )


Макрос развернется как число с плавающей точкой. На просто 16, как вы предположили, 16.0
Далее
if(i<16.0) {}

По правилам приведения типа, оба операнда будут приведены к double. И понеслась операция сравнения двух double.

Приведите явно результат макроса к целочисленному значению, типа

#define ADConLED(LED) ((unsigned) (LED*256)/Uin*Rnij/(Rnij+Rver) )
0
e_mc2, Вы правы и всё верно написали. Спасибо Вам большое. Всё заработало.
Заменил
#define ADConLED(LED) ( (LED*256)/Uin*Rnij/(Rnij+Rver) )

на
#define ADConLED(LED) ((unsigned) (LED*256)/Uin*Rnij/(Rnij+Rver) )
0
А, тьфу, не так тебя понял, сорри)
0
Для начинающих — если вы любите, как я библиотеки ложить в отдельные папки, то сослаться на хидер из соседней папки из текущей библиотеки можно так: #include "../hardware/sound.h"
0
Мы страдали без этого сакрального знания. СПА-СИ-БО!
А теперь серьезно. Начинающие обычно не заморачиваются структурированием. А как только — так уже и не совсем начинающие вроде. Это раз. И додумывается это гениальное решение с полпинка. Это два. И прямой/обратный слеш — та еще радость. Это три.
И «ложить» — вы серьезно??!!! =0
0
Я бы не рекомендовал подобный подход использовать повсеместно, особенно дле библиотек. В жизни проектов, чуть посложней «хеллоу ворлд», случается такая штука как рефакторинг. Когда меняется значительно структура кода и структура каталоов в частности. При этом в каждом файле, где есть такой #include "../hardware/sound.h", его придётся заменить, например, на "../HAL/sound.h" и т.д. И так каждый раз — мелочь, а не приятно. И в системе контроля версий «лишние» изменения появляются (вы ведь используете систему контроля версий?).
Короче говоря, не надо перекладывать на препроцессор, то чем должна заниматься система сборки. Собирать ли проект из IDE, или makefile-ом, всегда можно указать пути к каталогам с заголовками.
+1
Имеется такая задача:
#define FUNCTION "foo"

Нужно сделать так, чтобы была возможность вызвать эту foo(). Вся проблема в том, что описание изначально находится в кавычках. Можно ли каким-то образом от них избавиться на уровне препроцессора?
0
В общем случае — нет. стандартный Си-шный препроцессор такого не умеет. Закавычить какой либо токен легко, а вот обратное — нет. То есть имеет смысл задать
#define FUNCTION foo

без кавычек, а там где он нужен с кавычками — его закавычить:
#define __STR(X) #X
#define STR(X) __STR(X)
char funcName[] = STR(FUNCTION);
0
это понятно.
Вся проблема в том, что описание изначально находится в кавычках
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.