Проблемы с переменным макросом в C - PullRequest
4 голосов
/ 01 января 2011

У меня проблема с необязательными аргументами в операторах #define в C или, более конкретно, в gcc 4.2:

bool func1(bool tmp) { return false; }
void func2(bool tmp, bool tmp2) {}
#define CALL(func, tmp, ...) func(tmp, ##__VA_ARGS__)

int main() {
   // this compiles
   CALL(func2, CALL(func1, false), false);

   // this fails with: Implicit declaration of function 'CALL'
   CALL(func2, false, CALL(func1, false));
}

Это явно надуманный пример, но он показывает проблему. Кто-нибудь знает, как я могу получить необязательные аргументы, чтобы "решить" правильно?


Дополнительная информация: Если я уберу ## до __VA_ARGS__ и сделаю что-то вроде этого:

bool func2(bool tmp, bool tmp2) { return false; }
#define CALL(func, tmp, ...) func(tmp, __VA_ARGS__)

int main() {
   CALL(func2, false, CALL(func2, false, false));
}

Это компилируется, но больше не работает с нулевыми аргументами, поскольку разрешается в func(tmp, )

РЕДАКТИРОВАТЬ : Сразу после преобразования всего моего кода, чтобы полагаться на P99 вместо того, что у меня было раньше (что в итоге привело к серьезному нарушению моего кода, упс), я случайно обнаружил, что это работает:

bool func1(bool tmp) { return false; }
void func2(bool tmp, bool tmp2) {}
#define CALL2(func, tmp, p...) func(tmp, ##p)
#define CALL(func, tmp...) CALL2(func, tmp)

int main() {
   // works
   CALL(func2, CALL(func1, false), false);

   // ...also works
   CALL(func2, false, CALL(func1, false));
}

Компилирует и работает с любым количеством параметров (и правильные значения передаются и возвращаются), но ... это допустимо?

Ответы [ 3 ]

14 голосов
/ 01 января 2011

Оператор ## выполняет точную замену токена, поэтому в этом случае он пытается отправить токен "CALL(func1, false)" в качестве последнего аргумента функции func1 C .

Проблема в том, что CALL - это макрос, и вы не можете вкладывать вызовы переменных макросов в список ##__VA_ARGS__.

Причина, по которой он работает, когда внутренний макрос передается в качестве именованного аргумента, заключается в том, что препроцессор будет анализировать именованные аргументы для внутренних макросов, но не в списках ##__VA_ARGS__, где существует простая замена токена.

Один из способов решения этой проблемы - присвоить результат внутреннего CALL переменной-заполнителю и затем передать его макросу.

int main() {
   CALL(func2, CALL(func1, false), false);

   bool result = CALL(func1, false);
   CALL(func2, false, result);
}

Другой способ решить эту проблему - просто использовать __VA_ARGS__ в качестве единственного аргумента функции func, и это позволит вам передавать вызовы вложенных макросов, например:

#define CALL(func, ...) func(__VA_ARGS__)

int main() {
   CALL(func2, false, CALL(func2, false, false));
}

Давайте проанализируем вашу дилемму немного подробнее:

CALL(func2, false, CALL(func1, false))

В этом конкретном вызове макроса CALL теперь ("func2", "tmp", CALL(func1, false)) Поэтому он пытается вызвать func1, передавая tmp и, ну, CALL(func1, false).

Здесь проходит линия между препроцессором и фактическим компилятором C.

Препроцессор, как только он начинает выполнять подстановку, он выполняет синтаксический анализ, поэтому компилятор получает CALL(func1, false) как действительную функцию C, а не как макрос, потому что компилятор не знает только о макросах препроцессор делает.

4 голосов
/ 01 января 2011

Вы используете расширение gcc с конструкцией , ##. Вероятно, это не очень хорошая идея, если вы думаете о переносимости.

С небольшими усилиями вы можете создать макросы, которые могут реагировать на количество получаемых аргументов и выполнять правильную замену. P99 предоставляет помощников для этого:

#define CALL(...) P99_IF_EQ_2(P99_NARG(__VA_ARGS__))(dosomethingwithtwo(__VA_ARGS__))(dosomethingwithmore(__VA_ARGS__))

Но в вашем случае я думаю, что есть простое решение:

#define CALL(func, ...) func(__VA_ARGS__)
0 голосов
/ 01 января 2011

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

Теперь она компилируется и работает правильно для любого количества аргументов и любого количества вложений, как любой параметр.Спасибо всем!

...