Как сделать вариационный макрос (переменное число аргументов)

196

Я хочу написать макрос на C, который принимает любое количество параметров, а не конкретное число

пример:

#define macro( X )  something_complicated( whatever( X ) )

где Xлюбое количество параметров

Мне это нужно, потому что whateverон перегружен и может вызываться с 2 или 4 параметрами.

Я попытался определить макрос дважды, но второе определение переписало первое!

Компилятор, с которым я работаю, это g ++ (точнее, mingw)

Hasen
источник
8
Вы хотите C или C ++? Если вы используете C, почему вы компилируете компилятор C ++? Чтобы использовать правильные макросы C99, вы должны компилировать компилятор C, который поддерживает C99 (например, gcc), а не компилятор C ++, поскольку C ++ не имеет стандартных макрокоманд Variadic.
Крис Лутц
Ну, я предположил, что C ++ - это супер-набор C в этом отношении ..
hasen
tigcc.ticalc.org/doc/cpp.html#SEC13 содержит подробное объяснение вариационных макросов.
Гнуби
Хорошее объяснение и пример можно
найти
3
Для будущих читателей: C не является дочерним C ++. У них много общего, но есть правила, которые мешают им быть подмножеством и надмножеством друг друга.
Pharap

Ответы:

295

Способ C99, также поддерживается компилятором VC ++.

#define FOO(fmt, ...) printf(fmt, ##__VA_ARGS__)
Алекс Б
источник
8
Я не думаю, что C99 требует ## перед VA_ARGS . Это может быть просто VC ++.
Крис Лутц
98
Причина ## перед VA_ARGS заключается в том, что он глотает предыдущую запятую в случае, если список переменных-аргументов пуст, например. FOO ("a") расширяется до printf ("a"). Это расширение gcc (и, возможно, vc ++), C99 требует наличия хотя бы одного аргумента вместо эллипса.
jpalecek
110
##не нужен и не переносим. #define FOO(...) printf(__VA_ARGS__)делает работу переносным способом; fmtпараметр может быть опущен из определения.
alecov
4
IIRC, ## специфичен для GCC и позволяет передавать нулевые параметры,
говорит Моуг, верните Монику
10
Синтаксис ## работает также с llvm / clang и компилятором Visual Studio. Так что он может быть не переносимым, но поддерживается основными компиляторами.
К. Бирманн
37

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

Я действительно раздражен тем, что не могу комментировать исходное сообщение. В любом случае, C ++ не является надмножеством C. Это действительно глупо компилировать ваш код C с помощью компилятора C ++. Не делай то, что не делает Донни.

cmccabe
источник
8
«Это действительно глупо компилировать ваш код на C с помощью компилятора C ++» => Не считается таковым всеми (включая меня). См., Например, основные рекомендации по C ++: CPL.1 : предпочтение от C ++ до C , CPL.2: если вам нужно использовать C, используйте общее подмножество C и C ++ и скомпилируйте код C как C ++ . Мне трудно думать о том, какие «C-only-isms» действительно нужны, чтобы не иметь смысла программировать в совместимом подмножестве, и комитеты C и C ++ усердно работали над тем, чтобы сделать это совместимое подмножество доступным.
HostileFork говорит, что не доверяйте SE
4
@HostileFork Достаточно справедливо, хотя, конечно, люди C ++ хотели бы поощрять использование C ++. Другие не согласны, хотя; Linux Торвальдс, например, по - видимому , отверг множественным предложил патчи Linux-ядра , которые пытаются заменить идентификатор classс klassдля возможности осуществления компиляции с компилятором C ++. Также обратите внимание, что есть некоторые отличия, которые могут сбить вас с толку; например, троичный оператор не оценивается одинаково в обоих языках, а inlineключевое слово означает что-то совершенно другое (как я узнал из другого вопроса).
Кайл Стрэнд,
3
Для действительно кроссплатформенных системных проектов, таких как операционная система, вы действительно хотите придерживаться строгого C, потому что компиляторы C встречаются гораздо чаще. Во встроенных системах все еще существуют платформы без компиляторов C ++. (Существуют платформы только с проходимыми компиляторами C!) Компиляторы C ++ вызывают у меня нервозность, особенно для кибер-физических систем, и я думаю, я не единственный программист встраиваемых программ / C с таким чувством.
Мрачный
2
@downbeat. Независимо от того, используете ли вы C ++ для производства или нет, если вас беспокоит строгость, то возможность компилирования с C ++ дает вам магические возможности для статического анализа. Если у вас есть запрос, который вы хотите сделать из кодовой базы C ... интересно, используются ли определенные типы определенным образом, изучение того, как использовать type_traits, может создать для него целевые инструменты. То, что вы бы заплатили большие деньги за инструмент статического анализа C, может быть сделано с небольшим знанием C ++ и компилятором, который у вас уже есть ...
HostileFork говорит, что не доверяйте SE
1
Я говорю о вопросе Linux. (Я только что заметил, что там написано «Линукс Торвальдс», ха!)
печально
28

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

#define macro(ARGS) some_complicated (whatever ARGS)
// ...
macro((a,b,c))
macro((d,e))
eduffy
источник
21
Несмотря на то, что можно иметь макрос с переменным значением, полезно использовать двойные скобки.
Дэвид Родригес - dribeas
2
Компилятор XC от Microchip не поддерживает вариационные макросы, поэтому этот трюк с двойными скобками - лучшее, что вы можете сделать.
gbmhunter
10
#define DEBUG

#ifdef DEBUG
  #define PRINT print
#else
  #define PRINT(...) ((void)0) //strip out PRINT instructions from code
#endif 

void print(const char *fmt, ...) {

    va_list args;
    va_start(args, fmt);
    vsprintf(str, fmt, args);
        va_end(args);

        printf("%s\n", str);

}

int main() {
   PRINT("[%s %d, %d] Hello World", "March", 26, 2009);
   return 0;
}

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

#define PRINT //

или

#define PRINT if(0)print

Первый комментирует инструкции PRINT, второй запрещает инструкцию PRINT из-за условия NULL if. Если оптимизация установлена, компилятор должен удалить никогда не выполненные инструкции, такие как: if (0) print ("hello world"); или ((недействительно) 0);

jpalecek
источник
8
#define PRINT // не заменит PRINT //
bitc
8
#define PRINT if (0) print тоже не очень хорошая идея, потому что вызывающий код может иметь свой собственный else-if для вызова PRINT. Лучше: #define PRINT if (true), иначе print
bitc
3
Стандарт «ничего не делай, изящно» - это делать {} while (0)
vonbrand
Правильная ifверсия «не делай этого», которая учитывает структуру кода: if (0) { your_code } elseточка с запятой после того, как расширение макроса завершает else. В whileверсии выглядит следующим образом : while(0) { your_code } Проблема с do..whileверсией является то , что код в do { your_code } while (0)делаются один раз, гарантирован. Во всех трех случаях, если your_codeпусто, это правильно do nothing gracefully.
Джесси Чисхолм
4

объяснил для g ++ здесь, хотя это часть C99, поэтому должно работать для всех

http://www.delorie.com/gnu/docs/gcc/gcc_44.html

быстрый пример:

#define debug(format, args...) fprintf (stderr, format, args)
DarenW
источник
3
Вариационные макросы GCC не являются вариационными макросами С99. GCC имеет макросы C99 с переменным числом, но G ++ их не поддерживает, потому что C99 не является частью C ++.
Крис Латс
1
На самом деле g ++ будет компилировать макросы C99 в файлы C ++. Однако он выдаст предупреждение, если оно скомпилировано с параметром -pedantic.
Алекс Б
2
Это не С99. C99 использует макрос VA_ARGS ).
qrdl
1
C ++ 11 также поддерживает __VA_ARGS__, хотя они поддерживаются компиляторами и в более ранних версиях, как расширение.
Ethouris
1
Это не работает для printf ("привет"); там, где нет вар арг. Какой-нибудь общий способ исправить это?
БТР Найду