Порядок оценки макросов #define
EDIT: я думаю, что это лучше подходит для переполнения стека C++, поэтому я собираюсь повторно опубликовать это там.
Я работаю над системой меню с помощью макросов #define
(на основе прошивки Marlin на моем 3D-принтере). Я пытаюсь обобщить каждый пункт меню с помощью макроса #define MENU_ITEM_EDIT( )
. Полный код очень длинный, поэтому я опускаю куски кода для компактности. Однако вы можете найти полный код здесь: https://pastebin.com/h24itG9y
void draw_row(int row, char* value) { /* Stuff here*/ } // Рисует строку
char* uiToStr(int x) { /* Stuff here*/ } // Преобразует целое число без знака в строку
#define disp_array(display, value) display[value]
#define disp_func(display, value) display(value)
#define disp_char(display, value) display
#define MENU_ITEM_EDIT(min, max, rate, display, displayType, width, label, value) \
// Куча всего
draw_row(rowNum, disp_ ## displayType(display, value)); \
// Больше вещей
Использование макроса кода будет выглядеть следующим образом:
// Значения
const char item1[] = "Item 1", item2[] = "Item 2", item3[] = "Item 3";
const char* const itemList[] = {item1, item2, item3};
int currentItem = 0;
uint8_t item1Val = 10;
void menu1()
{
START_MENU();
MENU_ITEM_EDIT(0, 3, 1, itemList, array, 6, "Item:", currentItem);
MENU_ITEM_EDIT(0, 255, 1, uiToStr, func, 3, "Item 1 Value:", item1Val);
END_MENU();
}
Что отображается как:
Item: Item 1
Item 1 Value: 10
Для удобства в объявлениях моих меню я пытаюсь использовать общие типы меню, такие как uint8_t
, который находится в диапазоне от 0 до 255. Затем в меню я бы использовал __u255_t
вместо первых 6 аргументов.
#define __u255_t 0, 255, 1, uiToStr, func, 3
void menu2()
{
int item2Val = 10;
START_MENU();
MENU_ITEM_EDIT(__u255_t, "Item 2 Value", item2Val);
END_MENU();
}
Ранее я настроил его таким образом, что каждый раз, когда мне требовался другой тип меню, даже если я использовал его только один раз, мне приходилось определять для него тип и то, как он должен оцениваться в макросе. Таким образом, я могу использовать 1 аргумент вместо 6 для часто повторяющихся типов в строках. Однако мне по-прежнему нужна возможность использовать параметр с 6 аргументами, когда у меня есть строка с уникальным набором аргументов, например другой диапазон или список отображаемых строк.
Однако в этом примере программа не будет компилироваться, потому что она помещает __u255_t
в min
в макросе MENU_ITEM_EDIT()
, а не чем разложить __u255_t
на 6 значений и поместить их в первые 6 аргументов MENU_ITEM_EDIT()
. Макрос оценивается как:
MENU_ITEM_EDIT(__u255_t, "Item 2 Value", item2Val);
// Вычисляется как:
MENU_ITEM_EDIT( (0, 255, 1, uiToStr, func, 3), "Item 2 Value", item2val, , , , , );
Примечание. Я добавил дополнительный набор скобок, чтобы сделать его более понятным.
Но я хочу:
MENU_ITEM_EDIT(__u255_t, "Item 2 Value", item2Val);
// Evaluates as:
MENU_ITEM_EDIT(0, 255, 1, uiToStr, func, 3, "Item 2 Value", item2Val);
Я думал об использовании struct
, однако не уверен, что смогу, потому что в зависимости от строки строка value
в draw_row()
может быть функцией или массивом. Кто-нибудь может подсказать, как решить эту проблему?
Кроме того, дайте мне знать, если мне нужно добавить код, чтобы сделать мою проблему более понятной.
@Antyos, 👍1
Обсуждение1 ответ
Лучший ответ:
Вам нужно превратить ваш макрос в макрос. Звучит странно, я знаю. Но возьмем следующий пример:
#define foo bar, baz
#define foobar(X, Y, Z) something(X, Y, Z);
#define boofar(...) foobar(__VA_ARGS__)
boofar(foo, 4)
boofar
— это макрос, который принимает список переменных аргументов. Это расширяется в вызов вашего фактического вызова макроса, расширяя foo
в процессе. Затем ваш фактический макрос расширяется с уже существующими реальными аргументами. Дело в том, что foo
раскрывается перед передачей макросу, которому нужны несколько аргументов, находящихся внутри foo
.
Итак, цепочка расширения на самом деле:
boofar(foo, 4) ->
foobar(bar, baz, 4) ->
something(bar, baz, 4);
Запустив его через cpp
, вы получите:
# 1 "t.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 31 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 32 "<command-line>" 2
# 1 "t.c"
something(bar, baz, 4);
Гак. Реквизит для объяснения того, как заставить его работать, но все равно гак., @Duncan C
Да, это сбивает с толку, но ваше объяснение имеет смысл. Спасибо!, @Antyos
- C++ против языка Arduino?
- Как использовать SPI на Arduino?
- Какие накладные расходы и другие соображения существуют при использовании структуры по сравнению с классом?
- Ошибка: expected unqualified-id before 'if'
- Что лучше использовать: #define или const int для констант?
- Функции со строковыми параметрами
- Библиотека DHT.h не импортируется
- ошибка: ожидаемое первичное выражение перед токеном ','
Я согласен - это вопрос языка, а не Arduino, но краткий ответ заключается в том, что макросы не являются частью языка (C или C++); они являются функцией препроцессора (конечно, стандартной для компиляторов C и C++), которая оценивается во время компиляции, поэтому они должны быть полностью доступны для оценки в это время. Следовательно, будучи оцененными до того, как компилятор увидит ваш код, они также не имеют возможности перегрузки, включая списки параметров переменной длины, которые предоставляет C++., @JRobert
включая списки параметров переменной длины
-- на самом деле они это делают:__VA_ARGS__
и#define foo(...)
, @MajenkoМакросы прекомпилятора C/C++ — сплошной беспорядок. (Особенно макросы, которые принимают параметры, такие как те, которые вы используете.) Они не являются типобезопасными, и они разрешаются во время компиляции довольно запутанным образом. Есть и другие способы решения такого рода проблемы, @Duncan C