Можно ли использовать блок кода в качестве аргумента для макроса C?
У меня есть шаблон, который в основном какой-то шаблонный код с частью, которая меняется в середине
if(condition){
struct Foo m = start_stuff();
{ m.foo = bar(1,2); m.baz = 17; } //this part varies
end_stuff();
}
Можно ли сделать макрос, который принимает этот промежуточный блок кода в качестве аргумента? Правила раскрытия макросов в C кажутся ужасно сложными, поэтому я не уверен, есть ли какие-либо угловые случаи, которые могли бы привести меня в будущее (в частности, я не понимаю, как аргументы макросов разделяются, если мой код в нем запятые).
#define MY_MACRO(typ, do_stuff) do { \
if(condition){ \
struct typ m = start_stuff(); \
do_stuff; \
end_stuff(); \
} \
}while(0)
//usage
MY_MACRO(Foo, {
m.foo = bar(1,2);
m.baz = 17;
});
Пока единственное, что мне удалось придумать, это break
а также continue
быть захваченным, если я использую циклические операторы в своем макросе, и это было бы приемлемым компромиссом для моего конкретного случая использования.
редактировать: Конечно, я бы использовал функции, если бы мог. Пример, который я использовал в этом вопросе, упрощен и не демонстрирует биты, которые могут работать только с макро-магией.
7 ответов
Вы можете поместить блок кода в аргумент макроса при условии, что у него нет незащищенной запятой. В вашем примере единственная запятая в аргументе защищена, потому что она заключена в скобки.
Обратите внимание, что только круглые скобки охраняют запятые. Скобки ([]
) и брекеты ({}
) не делайте.
В качестве альтернативы вы можете использовать макрос, который предшествует вашему составному выражению, как показано ниже. Один из плюсов этого заключается в том, что все отладчики будут по-прежнему иметь возможность входить в составной оператор, что не имеет место в случае метода составного оператора как макроса.
//usage
MY_MACRO(Foo, condition) {
m.foo = bar(1,2);
m.baz = 17;
}
Используя некоторую магию goto (да, в некоторых случаях goto может быть злом, но у нас есть несколько альтернатив в C), макрос может быть реализован так:
#define CAT(prefix, suffix) prefix ## suffix
#define _UNIQUE_LABEL(prefix, suffix) CAT(prefix, suffix)
#define UNIQUE_LABEL(prefix) _UNIQUE_LABEL(prefix, __LINE__)
#define MY_MACRO(typ, condition) if (condition) { \
struct typ m = start_stuff(); goto UNIQUE_LABEL(enter);} \
if (condition) while(1) if (1) {end_stuff(); break;} \
else UNIQUE_LABEL(enter):
Обратите внимание, что это имеет небольшое влияние на производительность и занимаемую площадь, когда оптимизация компилятора отключена. Кроме того, отладчик, похоже, отскочит назад к строке MY_MACRO при запуске вызова функции end_stuff(), что на самом деле нежелательно.
Кроме того, вы можете захотеть использовать макрос внутри новой области видимости блока, чтобы избежать загрязнения вашей области видимости переменной 'm':
{MY_MACRO(Foo, condition) {
m.foo = bar(1,2);
m.baz = 17;
}}
Конечно, использование 'break' не внутри вложенного цикла в составном операторе пропустит end_stuff(). Чтобы те могли разорвать окружающий цикл и по-прежнему вызывать end_stuff(), я думаю, вам нужно заключить в составной оператор начальный токен и конечный токен, как в:
#define MY_MACRO_START(typ, condition) if (condition) { \
struct typ m = start_stuff(); do {
#define MY_MACRO_EXIT goto UNIQUE_LABEL(done);} while (0); \
end_stuff(); break; \
UNIQUE_LABEL(done): end_stuff();}
MY_MACRO_START(foo, condition) {
m.foo = bar(1,2);
m.baz = 17;
} MY_MACRO_END
Обратите внимание, что из-за 'перерыва' в этом подходе макрос MY_MACRO_EXIT будет использоваться только внутри цикла или переключателя. Вы можете использовать более простую реализацию, когда не внутри цикла:
#define MY_MACRO_EXIT_NOLOOP } while (0); end_stuff();}
Я использовал "условие" в качестве аргумента макроса, но вы также можете встроить его непосредственно в макрос при желании.
Вы можете поместить блок кода в макрос, но вы должны быть предупреждены, что отладчик значительно усложняет отладку. ИМХО лучше просто написать функцию или вырезать и вставить строки кода.
Как насчет указателей на функции вместо (и, возможно, inline
функции)?
void do_stuff_inner_alpha(struct Foo *m)
{
m->foo = bar(1,2); m->baz = 17;
}
void do_stuff_inner_beta(struct Foo *m)
{
m->foo = bar(9, 13); m->baz = 445;
}
typedef void(*specific_modifier_t)(struct Foo *);
void do_stuff(specific_modifier_t func)
{
if (condition){
struct Foo m = start_stuff();
func(&m); //this part varies
end_stuff();
}
}
int main(int argc, const char *argv[])
{
do_stuff(do_stuff_inner_beta);
return EXIT_SUCCESS;
}
Обратите внимание, что в C++ лямбда-выражение можно использовать следующим образом:
#include <iostream>
#define MY_MACRO(body) \
setup();\
body();\
teardown();\
int main() {
int a = 1;
MY_MACRO(([&]() mutable {
std::cout << "Look, no setup" << std::endl;
a++;
}));
std::cout << "a is now " << a << std::endl;
}
Если вы это сделаете, вы должны сначала подумать, должна ли вместо этого быть функция, которая явно принимает лямбда:
void withSetup(std::function<void ()> callback) {
setup();
callback();
teardown();
}
int main() {
withSetup([&]() {
doStuff();
});
}
"Это нормально?" может означать две вещи:
Это будет работать? Здесь ответ, как правило, да, но есть подводные камни. Одна из них, как упоминала Ричи, - это неохраняемая запятая. В общем, помните, что расширение макроса является операцией копирования и вставки, а препроцессор не понимает код, который копирует и вставляет.
Это хорошая идея? Я бы сказал, что ответ, как правило, нет. Это делает ваш код нечитаемым и сложным в обслуживании. В некоторых редких случаях это может быть лучше, чем альтернативы, если они реализованы правильно, но это исключение.
Прежде чем ответить на ваш вопрос "можно ли использовать макрос", я хотел бы знать, почему вы хотите преобразовать этот блок кода в макрос. Что вы пытаетесь получить и какой ценой?
Если один и тот же блок кода используется неоднократно, лучше преобразовать его в функцию, может быть, встроенную функцию и оставить ее компилятору, чтобы сделать ее встроенной или нет.
Если вы столкнетесь с сбоем \ проблемой, отладка макроса является утомительной задачей.