Можно ли использовать блок кода в качестве аргумента для макроса C?
У меня есть шаблон, который в основном представляет собой шаблонный код с частью, которая изменяется в середине
if(condition){
struct Foo m = start_stuff();
{ m.foo = bar(1,2); m.baz = 17; } //this part varies
end_stuff();
}
Можно ли сделать макрос taht, который принимает этот промежуточный код в качестве аргумента? Правила для макрорасширения в 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
получать захваченные, если я использую петлевые операторы в моем макросе, и это было бы приемлемым компромиссом для моего конкретного случая использования.
edit: Конечно, я бы использовал функции, если мог. Пример, который я использовал в этом вопросе, упрощен и не отображает биты, которые могут работать только с магии.
Ответы
Ответ 1
Вы можете поместить блок кода в аргумент макроса при условии, что он не имеет незащищенной запятой. В вашем примере единственная запятая в аргументе защищена, поскольку она окружена круглыми скобками.
Обратите внимание, что только скобки содержат запятые. Скобки ([]
) и фигурные скобки ({}
) не работают.
Ответ 2
В качестве альтернативы вы можете рассмотреть использование макроса, который предшествует вашему составному выражению, как показано ниже. Одним из плюсов этого является то, что все отладчики все равно смогут войти в ваш составной оператор, что не относится к методу составного оператора-макро-аргумента.
//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();}
Я использовал условие "условие" в качестве аргумента макроса, но вы можете также вставлять его непосредственно в макрос, если это необходимо.
Ответ 3
Вы можете поместить блок кода в макрос, но вы должны быть предупреждены о том, что отладка гораздо сложнее с помощью отладчика. IMHO лучше всего просто написать функцию или вырезать строки кода.
Ответ 4
Как насчет указателей на функции (и необязательно 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;
}
Ответ 5
"Все в порядке?" может означать две вещи:
-
Будет ли это работать? Здесь ответ, как правило, да, но есть подводные камни. Один, как упоминалось в rici, является незащищенной запятой. В принципе, помните, что расширение макросов - операция копирования и вставки, а препроцессор не понимает код, который он копирует и вставляет.
-
Это хорошая идея? Я бы сказал, что ответа обычно нет. Это делает ваш код нечитаемым и трудноподдерживаемым. В некоторых редких случаях это может быть лучше, чем альтернативы, если они реализованы хорошо, но это исключение.
Ответ 6
Прежде чем ответить на ваш вопрос "нормально ли использовать макрос", я хотел бы знать, почему вы хотите преобразовать этот блок кода в макрос. Что вы пытаетесь получить и по какой цене?
Если тот же блок кода, который вы используете повторно, лучше преобразовать его в функцию, возможно, встроенную функцию и оставить ее компилятору, чтобы сделать ее встроенной или нет.
Если вы столкнулись с проблемой crash\issue, отладка макроса - утомительная задача.