Подтвердить что ты не робот

Можно ли использовать блок кода в качестве аргумента для макроса 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: Конечно, я бы использовал функции, если мог. Пример, который я использовал в этом вопросе, упрощен и не отображает биты, которые могут работать только с магии.

4b9b3361

Ответ 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, отладка макроса - утомительная задача.