Вариативный макрос в препроцессоре C
Макрос с переменным числом аргументов является особенностью некоторых языков программирования , особенно препроцессора C , посредством которого можно объявить, что макрос принимает различное количество аргументов .
Макросы с переменными аргументами были представлены в 1999 году в версии ISO/IEC 9899:1999 ( C99 ) стандарта языка C и в 2011 году в версии ISO/IEC 14882:2011 ( C++11 ) стандарта языка C++ . [1] Поддержка макросов с переменным числом аргументов без аргументов была добавлена в C++20 и будет добавлена в C23 . [2] [3]
Синтаксис объявления
[ редактировать ]Синтаксис объявления аналогичен синтаксису функций с переменным числом аргументов : последовательность из трех точек " ... " используется для указания того, что необходимо передать один или несколько аргументов. Во время раскрытия макроса каждое появление специального идентификатора __VA_ARGS__ в списке замены макроса заменяется переданными аргументами.
Кроме того, перед макросом могут быть указаны обычные аргументы макроса. ...
, [4] но обычные аргументы не могут быть перечислены после ...
.
Никаких средств для доступа к отдельным аргументам в списке переменных аргументов или для выяснения их количества не предусмотрено. Однако можно написать макросы для подсчета количества переданных аргументов. [5]
В стандартах C99 и C++11 требуется хотя бы один аргумент, но начиная с C++20 это ограничение было снято с помощью функционального макроса __VA_OPT__ . Макрос __VA_OPT__ заменяется своим аргументом, если аргументы присутствуют, и опускается в противном случае. Однако обычные компиляторы также допускают передачу нулевых аргументов перед этим добавлением. [4] [6]
имен макросов в аргументе __VA_OPT__ Правила препроцессора C запрещают рекурсивное расширение . Однако это ограничение можно обойти до произвольного фиксированного числа рекурсивных расширений. [7]
Поддерживать
[ редактировать ]Некоторые компиляторы поддерживают макросы с переменными аргументами при компиляции кода C и C++: GNU Compiler Collection 3.0, [4] Кланг (все версии), [8] Визуальная Студия 2005 , [6] C++Builder 2006 и Oracle Solaris Studio (ранее Sun Studio) Forte Developer 6, обновление 2 (версия C++ 5.3). [9] GCC также поддерживает такие макросы при компиляции Objective-C .
Поддержка макроса __VA_OPT__ для поддержки нулевых аргументов была добавлена в GNU Compiler Collection 8. [10] Кланг 6, [11] и Visual Studio 2019 . [12]
Пример
[ редактировать ]Если printf
-подобная функция dbgprintf()
если бы были желательны, которые принимали бы в качестве аргументов файл и номер строки, из которой он был вызван, применяется следующее решение.
- Наша реализованная функция
void realdbgprintf (const char *SourceFilename, int SourceLineno, const char *CFormatString, ...);
Из-за ограничений поддержки переменных макросов в C++11 следующее: простое решение может потерпеть неудачу, и поэтому его следует избегать:
#define dbgprintf(cformat, ...) \ realdbgprintf (__FILE__, __LINE__, cformat, __VA_ARGS__)
Причина в том, что
dbgprintf("Hallo")
расширяется до
где запятая перед закрывающей скобкой приведет к синтаксической ошибке.realdbgprintf (__FILE__, __LINE__, "Hallo", )
- GNU C++ поддерживает непереносимое расширение, которое решает эту проблему.
#define dbgprintf(cformat, ...) \ realdbgprintf (__FILE__, __LINE__, cformat, ##__VA_ARGS__)
- C++20 в конечном итоге поддерживает следующий синтаксис.
#define dbgprintf(cformat, ...) \ realdbgprintf (__FILE__, __LINE__, cformat __VA_OPT__(,) __VA_ARGS__)
- Используя строку cformat как часть переменных аргументов, мы можем
обойти вышеупомянутые несовместимости. Это сложно, но портативный.
#define dbgprintf(...) realdbgprintf (__FILE__, __LINE__, __VA_ARGS__)
dbgprintf()
тогда можно было бы назвать как
dbgprintf ("Hello, world");
который расширяется до
realdbgprintf (__FILE__, __LINE__, "Hello, world");
Другой пример:
dbgprintf("%d + %d = %d", 2, 2, 5);
который расширяется до
realdbgprintf(__FILE__, __LINE__, "%d + %d = %d", 2, 2, 5);
Без вариационных макросов запись оберток в printf
напрямую невозможно. Стандартный обходной путь — использовать функциональность stdargs C/C++ и вызвать функцию vprintf
вместо.
Завершающая запятая
[ редактировать ]Существует проблема переносимости при создании завершающей запятой с пустыми аргументами для переменных макросов в C99 . Некоторые компиляторы (например, Visual Studio, если не используется новый препроцессор, соответствующий стандарту) [6] ) автоматически удалит конечную запятую. Другие компиляторы (например: GCC [4] ) поддерживаю ставлю ##
перед __VA_ARGS__
.
# define MYLOG(FormatLiteral, ...) fprintf (stderr, "%s(%u): " FormatLiteral "\n", __FILE__, __LINE__, __VA_ARGS__)
Следующее приложение работает
MYLOG("Too many balloons %u", 42);
который расширяется до
fprintf (stderr, "%s(%u): " "Too many balloons %u" "\n", __FILE__, __LINE__, 42);
что эквивалентно
fprintf (stderr, "%s(%u): Too many balloons %u\n", __FILE__, __LINE__, 42);
Но посмотрите на это приложение:
MYLOG("Attention!");
который расширяется до
fprintf (stderr, "%s(%u): " "Attention!" "\n", __FILE__, __LINE__, );
который генерирует синтаксическую ошибку с GCC.
GCC поддерживает следующее (непереносимое) расширение:
# define MYLOG(FormatLiteral, ...) fprintf (stderr, "%s(%u): " FormatLiteral "\n", __FILE__, __LINE__, ##__VA_ARGS__)
который удаляет конечную запятую, когда __VA_ARGS__
пусто.
C23 решает эту проблему, вводя __VA_OPT__
как С++. [3]
Альтернативы
[ редактировать ]До появления переменных аргументов в C99 было довольно распространено использование двойных вложенных круглых скобок для использования переменного количества аргументов, которые могли быть переданы в функцию. printf()
функция:
#define dbgprintf(x) realdbgprintf x
dbgprintf()
тогда можно было бы назвать так:
dbgprintf (("Hello, world %d", 27));
который расширяется до:
realdbgprintf ("Hello, world %d", 27);
Ссылки
[ редактировать ]- ^ Рабочий проект изменений для синхронизации препроцессора C99 – http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2004/n1653.htm
- ^ «Пропуск и удаление запятой» . 18 июня 2017 г. Проверено 24 декабря 2022 г.
- ^ Перейти обратно: а б «WG14 – N3033: Пропуск и удаление запятой» . 20 июля 2022 г.
- ^ Перейти обратно: а б с д Вариативные макросы – использование коллекции компиляторов GNU (GCC)
- ^ Лоран Денио (16 января 2006 г.). "__ВА_НАРГ__" . новостей : comp.std.c. Группа Usenet: [электронная почта защищена] .
- ^ Перейти обратно: а б с Вариативные макросы (C++)
- ^ Рекурсивные макросы с C++20 __VA_OPT__
- ^ Изменение исходного кода Clang, в котором упоминается поддержка __VA_ARGS__ (29 июля 2006 г.), обратите внимание, что исходный код Clang был открыт в 2007 году. http://llvm.org/viewvc/llvm-project?view=revision&revision=38770
- ^ Сравнение функций Sun Studio – http://developers.sun.com/sunstudio/support/CCcompare.html .
- ^ «Поддержка C++2a в GCC» . Проверено 14 июня 2018 г.
- ^ «Поддержка C++ в Clang» . Проверено 14 июня 2018 г.
- ^ «Обзор нового препроцессора MSVC» . 10 сентября 2020 г. Проверено 8 декабря 2020 г.