Может ли макрос C содержать временные переменные? - PullRequest
10 голосов
/ 07 января 2012

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

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

   /* do something */
   return 42;
}

Форма МАКРО:

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

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

Ответы [ 9 ]

25 голосов
/ 07 января 2012

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

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

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

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

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

7 голосов
/ 07 января 2012

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

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

#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, при условии, что никто не определит переменную с использованием этого имени (что является странным предположением, учитывая, что вы только что это сделали).

6 голосов
/ 07 января 2012
Макросы

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

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

5 голосов
/ 07 января 2012

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

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

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

#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; \
    })
2 голосов
/ 07 января 2012

Ответ Эльдара показывает вам большинство подводных камней макропрограммирования и несколько полезных (но не стандартных) расширений 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 вычисляет только выражение для типа во время компиляции, а не для его значения, так что это не будетt оцените F дважды.

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

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

0 голосов
/ 16 ноября 2015

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

#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;
}
0 голосов
/ 07 января 2012

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

0 голосов
/ 07 января 2012

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

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

0 голосов
/ 07 января 2012

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

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

...