Можно ли использовать блок кода в качестве аргумента для макроса 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();
  });
}

"Это нормально?" может означать две вещи:

  1. Это будет работать? Здесь ответ, как правило, да, но есть подводные камни. Одна из них, как упоминала Ричи, - это неохраняемая запятая. В общем, помните, что расширение макроса является операцией копирования и вставки, а препроцессор не понимает код, который копирует и вставляет.

  2. Это хорошая идея? Я бы сказал, что ответ, как правило, нет. Это делает ваш код нечитаемым и сложным в обслуживании. В некоторых редких случаях это может быть лучше, чем альтернативы, если они реализованы правильно, но это исключение.

Прежде чем ответить на ваш вопрос "можно ли использовать макрос", я хотел бы знать, почему вы хотите преобразовать этот блок кода в макрос. Что вы пытаетесь получить и какой ценой?

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

Если вы столкнетесь с сбоем \ проблемой, отладка макроса является утомительной задачей.

Другие вопросы по тегам