C вариативный макрос не компилируется

У меня есть проблема с этим кодом, который я написал. GCC это не нравится:

#define _DEBUG_ADD(string, ...)                     \
do{                                                 \
if (EVALUATE_TYPE(string)){                         \
size_t  size = strlen(string) + BUFFER_SIZE_DEBUG;  \
char    *buffer = alloca(size);                     \
bzero(buffer, size);                                \
snprintf(buffer, size, string, __VA_ARGS__);        \
fwrite(buffer, strlen(buffer), 1, DEBUG_STREAM); }} \
while(0)

Но gcc отображает эту ошибку:

../debug.h:33:42: error: expected expression before ')' token
                    snprintf(buffer, size, string, __VA_ARGS__);

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

Может ли кто-нибудь указать на мою ошибку? Я полностью потерян.

изменить :

я так использую

_DEBUG_ADD("Bbox found @ %f %f %f %f", box[0], box[1], box[2], box[3]);

person Michael Vouriz    schedule 17.09.2017    source источник
comment
Покажите, как вы используете этот макрос   -  person Basile Starynkevitch    schedule 17.09.2017
comment
Не используйте макрос там, где подойдет и функция! Если бы вы правильно отформатировали этот беспорядок, проблема была бы очевидной.   -  person too honest for this site    schedule 17.09.2017
comment
@BasileStarynkevitch Я использую это так: _DEBUG_ADD(Bbox found @ %f %f %f %f, box[0], box[1], box[2], box[3]);   -  person Michael Vouriz    schedule 17.09.2017
comment
Это должно войти в ваш вопрос (не комментируйте свой вопрос, а редактируйте его)   -  person Basile Starynkevitch    schedule 17.09.2017
comment
@Olaf Я согласен, но я хочу попробовать использовать макрос.   -  person Michael Vouriz    schedule 17.09.2017
comment
Вы уверены, что обратные косые черты являются последними символами (т.е. без пробелов после них) их строк?   -  person Basile Starynkevitch    schedule 17.09.2017
comment
Я не думаю, что пример использования, который вы показали, вызывает проблему, но вместо этого это случай, когда после него есть строки формата без аргументов. в таком случае __VA_ARGS__ будет скомпилировано в ничто, и у вас будет запятая, за которой следуют закрывающие скобки   -  person MByD    schedule 17.09.2017
comment
Не воспроизводится, опубликуйте минимально воспроизводимый пример. Примечание _DEBUG_ADD зарезервировано, использование этого идентификатора — UB.   -  person n. 1.8e9-where's-my-share m.    schedule 17.09.2017
comment
stackoverflow.com/a/16502124/841108 может вас вдохновить   -  person Basile Starynkevitch    schedule 17.09.2017
comment
Вы можете исправить это, добавив два # перед __VA_ARGS__, и тогда предыдущая запятая будет удалена, например. sprintf(buffer, size, string, ##__VA_ARGS__). Я не публикую это как ответ, так как не знаю, что это настоящая проблема.   -  person MByD    schedule 17.09.2017
comment
@BasileStarynkevitch Конечно, пробелов нет   -  person Michael Vouriz    schedule 17.09.2017
comment
@MByD не работает. Обычно мне не нужно использовать токены   -  person Michael Vouriz    schedule 17.09.2017
comment
Предположим, что ваш источник foo.c; получить необработанную предварительно обработанную форму, используя что-то вроде gcc -C -E foo.c|grep -v '^#' > foo.i, затем скомпилировать с помощью gcc -Wall -Wextra -c foo.i и внимательно изучить сообщения об ошибках и местоположения (они будут указывать на foo.i, и вы поймете, что не так)   -  person Basile Starynkevitch    schedule 17.09.2017
comment
Обратите внимание, что имена, начинающиеся со знака подчеркивания и либо с другого знака подчеркивания, либо с заглавной буквы, зарезервированы реализацией для любого использования. Не создавайте свои собственные имена, начинающиеся со знака подчеркивания (упрощенное правило, но безопасное), или читайте детали из §7.1.3 Зарезервированные идентификаторы в стандарте C (и аналогичные разделы в других стандартах, таких как POSIX) и следуйте им. Не используйте _DEBUG_… в качестве придуманного имени, если вам дорого ваше здравомыслие.   -  person Jonathan Leffler    schedule 17.09.2017
comment
@MichaelVouriz: Значит, ты предпочитаешь прыгать с крыши, чтобы посмотреть, не больно ли это? Есть некоторые вещи, которые являются просто плохой идеей и не дают никакого знания.   -  person too honest for this site    schedule 18.09.2017


Ответы (1)


Если я возьму фрагменты кода, которые вы показали, и заполню недостающие биты, чтобы получить полный компилируемый исходный файл, я не получу никаких ошибок:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define _DEBUG_ADD(string, ...)                     \
do{                                                 \
if (EVALUATE_TYPE(string)){                         \
size_t  size = strlen(string) + BUFFER_SIZE_DEBUG;  \
char    *buffer = alloca(size);                     \
bzero(buffer, size);                                \
snprintf(buffer, size, string, __VA_ARGS__);        \
fwrite(buffer, strlen(buffer), 1, DEBUG_STREAM); }} \
while(0)

#define EVALUATE_TYPE(s) 1
#define BUFFER_SIZE_DEBUG 128
#define DEBUG_STREAM stderr

void test(double box[4])
{
  _DEBUG_ADD("Bbox found @ %f %f %f %f", box[0], box[1], box[2], box[3]);
}

-->

$ gcc -fsyntax-only -Wall test.c
$

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

Однако в данном случае у меня есть сильное подозрение, что ваша проблема на самом деле была вызвана таким кодом, как этот:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define _DEBUG_ADD(string, ...)                     \
do{                                                 \
if (EVALUATE_TYPE(string)){                         \
size_t  size = strlen(string) + BUFFER_SIZE_DEBUG;  \
char    *buffer = alloca(size);                     \
bzero(buffer, size);                                \
snprintf(buffer, size, string, __VA_ARGS__);        \
fwrite(buffer, strlen(buffer), 1, DEBUG_STREAM); }} \
while(0)

#define EVALUATE_TYPE(s) 1
#define BUFFER_SIZE_DEBUG 128
#define DEBUG_STREAM stderr

void test(void)
{
  _DEBUG_ADD("got here 1");
}

который выдает почти такое же сообщение об ошибке, которое вы показали:

$ gcc -fsyntax-only -Wall test.c
test.c: In function ‘test’:
test.c:11:43: error: expected expression before ‘)’ token
 snprintf(buffer, size, string, __VA_ARGS__);        \
                                           ^
test.c:21:3: note: in expansion of macro ‘_DEBUG_ADD’
   _DEBUG_ADD("got here 1");
   ^~~~~~~~~~

Когда вы не даете _DEBUG_ADD никаких аргументов после строки формата, __VA_ARGS__ расширяется до нуля, поэтому «собственный компилятор» видит

snprintf(buffer, size, string, );

что действительно является синтаксической ошибкой. Это то, что GNU расширение для удаления запятых для: если вы поместите ## между , и __VA_ARGS__, запятая будет удалена, когда __VA_ARGS__ превратится в ничто.

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define _DEBUG_ADD(string, ...)                     \
do{                                                 \
if (EVALUATE_TYPE(string)){                         \
size_t  size = strlen(string) + BUFFER_SIZE_DEBUG;  \
char    *buffer = alloca(size);                     \
bzero(buffer, size);                                \
snprintf(buffer, size, string, ##__VA_ARGS__);      \
fwrite(buffer, strlen(buffer), 1, DEBUG_STREAM); }} \
while(0)

#define EVALUATE_TYPE(s) 1
#define BUFFER_SIZE_DEBUG 128
#define DEBUG_STREAM stderr

void test(void)
{
  _DEBUG_ADD("got here 1");
}

-->

$ gcc -fsyntax-only -Wall test.c
$

К сожалению, это расширение доступно только в GCC и Clang. Я понимаю, что комитеты C и C++ говорят о добавлении сопоставимой, но несовместимой функции Real Soon Now (см. комментарии на этот вопрос и ответы на него, а также документы комитета C N2023 и N2153), но даже если они это сделают, вероятно, пройдет десятилетие или около того, прежде чем это станет достаточно повсеместным для использования.

Кстати, имя _DEBUG_ADD начинается с подчеркивания. Все имена, начинающиеся с подчеркивания, зарезервированы для внутреннего использования компилятором C и библиотекой, по крайней мере, в некоторых контекстах. Пока у вас гораздо больше опыта работы с языком, вы не должны давать в своем коде имена, начинающиеся с подчеркивания. (Можно использовать элементы с именами, начинающимися с подчеркивания, например __VA_ARGS__ и _IONBF, но только если они задокументированы.)

person zwol    schedule 17.09.2017
comment
Любой URL о том, что обсуждает комитет C? - person Basile Starynkevitch; 17.09.2017
comment
Аналогично stackoverflow.com/a/16502124/841108, упомянутому в комментарии к вопросу. - person Basile Starynkevitch; 17.09.2017
comment
@BasileStarynkevitch Эм, не так давно это появилось в комментариях к другому вопросу. сейчас пытаюсь раскопать. Надеюсь, модераторы не удалили эти комментарии. - person zwol; 17.09.2017
comment
N2153 выглядит так, как будто это последний документ по этому вопросу из «Почтовой рассылки Маркхэма». - person Jonathan Leffler; 17.09.2017