Может ли макрос C содержать временные переменные?

У меня есть функция, которую мне нужно макровизировать. Функция содержит временные переменные, и я не могу вспомнить, существуют ли какие-либо правила использования временных переменных в макроподстановках.

long fooAlloc(struct foo *f, long size)
{
   long      i1, i2;
   double   *data[7];

   /* do something */
   return 42;
}

Форма MACRO:

#define ALLOC_FOO(f, size) \
{\
   long      i1, i2;\
   double   *data[7];\
\
   /* do something */ \
}

Это нормально? (то есть никакого неприятного побочного эффекта - кроме обычных: не "безопасный тип" и т.д.). Кстати, я знаю, что "макросы злы" - я просто должен использовать его в этом случае - не так много выбора.

Ответ 1

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

  • У макроса нет оператора return. Вы можете использовать трюк do while.

    #define macro(x) do { int y = x; func(&y); } while (0)
    
  • Вы нацеливаете только GCC.

    #define min(x,y) ({ int _x = (x), _y = (y); _x < _y ? _x : _y; })
    

Это поможет, если вы объясните, почему вы должны использовать макрос (есть ли в вашем офисе "макропомощи" или что-то в этом роде?). В противном случае мы не сможем помочь.

Ответ 2

Во-первых, я настоятельно рекомендую встроенные функции. Макросов очень мало, и они не могут, и они гораздо чаще будут делать то, что вы ожидаете.

Одна ложь макросов, которую я не видел в других ответах, - это затенение имен переменных.
Предположим, вы определили:

#define A(x) { int temp = x*2; printf("%d\n", temp); }

И кто-то использовал его так:

int temp = 3;
A(temp);

После предварительной обработки код:

int temp = 3;
{ int temp = temp*2; printf("%d\n", temp); }

Это не работает, потому что внутренняя температура затеняет внешний.
Общим решением является вызов переменной __temp, предполагая, что никто не будет определять переменную с использованием этого имени (что является странным предположением, учитывая, что вы только что это сделали).

Ответ 3

C макросы - это (относительно простые) текстовые подстановки.

Итак, вопрос, который вы, возможно, спрашиваете: могу ли я создать блоки (также называемые составными операторами) в функции, подобной приведенной ниже в примере?

void foo(void)
{
    int a = 42;
    {   
        int b = 42;
        {
            int c = 42; 
        } 
    }
}

и ответ да.

Теперь, когда @DietrichEpp упомянул об этом в своем ответе, если макрос представляет собой составной оператор, как в вашем примере, хорошей практикой является приложение макросов с помощью do { ... } while (0), а не только { ... }. Ссылка ниже объясняет, в какой ситуации do { ... } while (0) в макросе пытается предотвратить:

http://gcc.gnu.org/onlinedocs/cpp/Swallowing-the-Semicolon.html

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

Ответ 4

Это в основном ОК, за исключением того, что макросы обычно заключены в do { ... } while(0) (посмотрите на этот вопрос для объяснений):

#define ALLOC_FOO(f, size) \
    do { \
        long      i1, i2;\
        double   *data[7];\
        /* do something */ \
    } while(0)

Кроме того, насколько ваша исходная функция fooAlloc возвращает long, вам нужно изменить свой макрос, чтобы сохранить результат как-то иначе. Или, если вы используете GCC, вы можете попробовать составной оператор:

#define ALLOC_FOO(f, size) \
    ({ \
        long      i1, i2;\
        double   *data[7];\
        /* do something */ \
        result; \
    })

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

#define ALLOC_FOO(f, size) \
    ({ \
        typeof(f) __f = (f);\
        typeof(size) __size = (size);\
        long      i1, i2;\
        double   *data[7];\
        /* do something */ \
        result; \
    })

Ответ 5

Ответ Eldar показывает вам большинство ошибок макропрограммирования и некоторого полезного (но не стандартного) расширения gcc.

Если вы хотите придерживаться стандарта, может быть полезно сочетание функций макросов (для общей) и inline (для локальных переменных).

inline
long fooAlloc(void *f, size_t size)
{
   size_t      i1, i2;
   double   *data[7];

   /* do something */
   return 42;
}


#define ALLOC_FOO(T) fooAlloc(malloc(sizeof(T)), sizeof(T))

В таком случае использование sizeof оценивает выражение для типа во время компиляции, а не для его значения, поэтому это не будет дважды оценивать F.

Кстати, "размеры" обычно должны быть напечатаны с помощью size_t, а не с long или аналогичным.

Изменить: Что касается вопроса Джонатана о функциях inline, я написал что-то о модели inline модели C99, здесь.

Ответ 6

Да, он должен работать, когда вы используете структуру блоков, а временные переменные объявляются во внутренней области этого блока.

Обратите внимание, что последний\после} является избыточным.

Ответ 7

Они могут. Они часто не должны.

Почему эта функция должна быть макросом? Не могли бы вы включить его?

Ответ 8

Если вы используете С++, используйте inline или используйте -o3 с gcc, он будет встроить все функции для вас. Я до сих пор не понимаю, почему вам нужно макросоциализировать эту функцию.

Ответ 9

Не идеальное решение: (не работает с рекурсивными макросами, например несколькими петлями внутри друг друга)

#define JOIN_(X,Y) X##Y
#define JOIN(X,Y) JOIN_(X,Y)
#define TMP JOIN(tmp,__LINE__)

#define switch(x,y) int TMP = x; x=y;y=TMP

int main(){
  int x = 5,y=6;
  switch(x,y);
  switch(x,y);
}

станет после запуска препроцессора:

int main(){
   int x=5,y=6;
   int tmp9 = x; x=y; y=tmp9;
   int tmp10 = x; x=y; y=tmp10;
}