Case вариативный макрос в C

У меня есть 2 макроса-оболочки для утверждения входных параметров функции:

/**
 * @brief   An assert wrapper with no value return in case assert fails.
 * @param   x_: value to test for being non zero.
 */
#define UTIL_ASSERT_VOID(x_)                                                \
    assert_param(x_);                                                       \
    if (!x_)                                                                \
        return;                                                             \

/**
 * @brief   An assert wrapper with a value return in case assert fails.
 * @param   x_: value to test for being non zero.
 */
#define UTIL_ASSERT_VAL(x_, ret_)                                           \
    assert_param(x_);                                                       \
    if (!x_)                                                                \
        return ret_;                                                        \

Первый используется в функциях, возвращающих void, а второй — в функциях, возвращающих non-void. Мне было интересно, либо в C11 (или ранее) есть механизм, позволяющий использовать только один макрос с переменным количеством параметров. В зависимости от того, сколько параметров предоставлено макросу (1 или 2), будет скомпилирован return или return ret_.


person Łukasz Przeniosło    schedule 09.03.2019    source источник
comment
Пахнет проблемой XY. Что вам, вероятно, следует сделать, так это просто assert(param1); assert(param2); .... Как имеет смысл возвращаться из функции, когда сработало утверждение?   -  person Lundin    schedule 11.03.2019
comment
@Lundin в общей ситуации это не имеет смысла. Но во-первых, вы всегда должны обрабатывать все случаи, поэтому вы всегда возвращаетесь после утверждения, несмотря ни на что. Во-вторых, это не имеет смысла, только если программа запускается в среде, контролируемой операционной системой, которая остановит ее при утверждении. Во встроенной системе без ОС и с большим количеством потоков могут произойти плохие вещи, когда возникают ошибки сегментации. Также зависит от реализации утверждения, это не всегда останавливает поток.   -  person Łukasz Przeniosło    schedule 11.03.2019
comment
В реальной системе вы всегда будете возвращать код ошибки при ошибке; вы бы не просто тихо упали в случае ошибок в функции void. Так что ваш сценарий нереалистичен. У вас не будет одной функции, возвращающей void, и другой, возвращающей int, а будет один тип функции, возвращающий err_t или что-то еще. Кроме того, в голых металлических системах следует избегать использования assert().   -  person Lundin    schedule 11.03.2019
comment
@Lundin assert — это инструмент отладки. Он даже не скомпилирован в релизную сборку. Его существование имеет смысл только для целей разработки и не принимается во внимание в сборке релиза. Если вам кажется, что так и должно быть, функциональные возможности построены неправильно. Вы описываете разные механизмы.   -  person Łukasz Przeniosło    schedule 11.03.2019
comment
Вот что я имею в виду, в случае сборки релиза assert не будет, и поэтому ваша функция void просто молча вернется, и программа не сможет сказать, что произошла ошибка. Это просто плохой дизайн. Кроме того, встроенные системы чаще всего оставляют проверку параметров вызывающей стороне, поскольку это может быть дорого.   -  person Lundin    schedule 11.03.2019
comment
Эта тема слишком велика, чтобы ее развивать. Если вы коммутируете двигатель в этой функции, то да, проверки может быть слишком много. Но так ли это? Возможно, система работает достаточно быстро и т. д. Это не так. В этих примерных макросах просто отсутствуют операторы #ifdef DEBUG, которые есть в моем коде, но не здесь. В этом вы правы.   -  person Łukasz Przeniosło    schedule 11.03.2019
comment
Я хочу сказать, что в реальной системе у вас будет только один тип результата err_t, поэтому вся эта тема не имеет смысла. И после того, как вы реализовали err_t, нет смысла раздельно обрабатывать в режиме отладки и релиза, так как вы всегда будете обнаруживать ошибки независимо от сборки. Вы можете заменить макрос на if(param == 0) { return ERR_BAD_PARAM; }.   -  person Lundin    schedule 11.03.2019
comment
Очевидно, что система с одним типом возврата была бы отличной. Но это недоступно. Кроме того, безусловно, существуют функции, возвращающие void, и это неплохая практика, если предположить, что они всегда работают, если входные параметры не являются нулевыми указателями.   -  person Łukasz Przeniosło    schedule 11.03.2019
comment
Функции без обработки ошибок являются плохой практикой. Так что если у вас есть база кода без обработки ошибок... это отстой. Но вы можете сделать эту кодовую базу еще хуже, молча пропустив прошлые ошибки, молча возвращаясь из функций при ошибке. Было бы лучше помигать каким-нибудь светодиодом, напечатать какое-нибудь сообщение, зациклить программу и дождаться сторожевого таймера и т.д.   -  person Lundin    schedule 11.03.2019
comment
Это то, что можно сделать в assert_param. Даже если вы застряли там в вечном цикле, вам нужен оператор return ради корректности кода, о котором вы так часто упоминаете.   -  person Łukasz Przeniosło    schedule 11.03.2019
comment
Извините, я не понимаю... зачем вам оператор return в вечном цикле?   -  person Lundin    schedule 11.03.2019
comment
Если вы не знаете, что это там   -  person Łukasz Przeniosło    schedule 11.03.2019


Ответы (2)


Вы можете сделать это следующим образом:

#define UTIL_ASSERT(x_, ...)                                                \
    assert_param(x_);                                                       \
    if (!x_)                                                                \
        return __VA_ARGS__;

Но помните, вы не можете гарантировать только 1 параметр в этом вариативном макросе, поэтому вам нужно правильно его использовать.

Обновление: благодаря этой ветке я пришел чтобы лучше подойти:

void assert_param(int x);

#define UTIL_ASSERT_1(x_)   do { assert_param(x_); if (!x_) return; } while(0)

#define UTIL_ASSERT_2(x_, ret_)   do { assert_param(x_); if (!x_) return ret_; } while(0)     

#define GET_MACRO(_1,_2,NAME,...) NAME
#define UTIL_ASSERT(...) GET_MACRO(__VA_ARGS__, UTIL_ASSERT_2, UTIL_ASSERT_1)(__VA_ARGS__)


int foo() {
     UTIL_ASSERT(0,1);
}

void doo() {
     UTIL_ASSERT(0);
}

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

person Afshin    schedule 09.03.2019
comment
Черт, не мог понять эту семантику, имеет смысл! И да, я понимаю недостатки безопасности, как и во всех макросах, но я действительно терпеть не могу подавляющее копирование кода. - person Łukasz Przeniosło; 09.03.2019
comment
Обратите внимание, что это зависит от нестандартного расширения, которое позволяет вам опускать аргументы ..., и оно будет генерировать предупреждения, если вы скомпилируете с -pedantic. (gcc/клэнг). - person PSkocik; 09.03.2019
comment
@Bremen Я думаю, ты тоже должен уметь писать макросы получше. Я написал самую простую форму на самом деле. - person Afshin; 09.03.2019
comment
@Afshin это простая функциональность, я не могу придумать более сложный макрос. У вас есть что-то конкретное? - person Łukasz Przeniosło; 09.03.2019
comment
@PSkocik да, это самая простая форма. У меня сейчас экзамен, я постараюсь найти лучшее решение позже. :) - person Afshin; 09.03.2019
comment
@Bremen Я думал использовать __VA_OPT__, чтобы расширить макрос до 2 разных, один с 1 параметром, а другой с 2. Поэтому я думаю, что таким образом мы должны делать ошибки, если макрос используется неправильно. Но я действительно должен подумать и попробовать это сам. Хотя это будет C++2a. - person Afshin; 09.03.2019
comment
@Afshin не только __VA_OPT__ C++? - person Łukasz Przeniosło; 09.03.2019
comment
@ Бремен, да, кажется. Но у меня есть ощущение, что есть обходной путь. Кажется, я где-то видел что-то подобное... - person Afshin; 09.03.2019
comment
В руководстве @Bremen gcc говорится: __VA_OPT__ is also available in GNU C and GNU C++. . - person Afshin; 09.03.2019
comment
@Afshin Действительно, мой компилятор gcc поддерживает __VA_OPT__, но я не уверен, что его можно использовать в этом контексте. Кажется, он предназначен для использования функций с переменным числом аргументов, как показано в примере здесь gcc. gnu.org/onlinedocs/cpp/Variadic-Macros.html - person Łukasz Przeniosło; 09.03.2019
comment
@Bremen Обновленный ответ с лучшим подходом. - person Afshin; 09.03.2019

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

#define ASSERT0(X, RET, ...) 
   /* put your stuff here that only uses X and RET, and ignores the ... */

Как вы можете видеть, это получает три аргумента или больше. И вам нужно будет сделать так, чтобы RET был просто пустым токеном для случая, когда вам это нужно.

Теперь вы можете поместить обертку вокруг этого

#define ASSERT1(...) ASSERT0(__VA_ARGS__)

Это гарантирует, что запятые, которые могут находиться внутри отдельных аргументов, будут рассматриваться как разделители аргументов для ASSERT0.

Затем макрос уровня пользователя может быть

#define MY_ASSERT(...) ASSERT1(__VA_ARGS__, ,)

Это гарантирует, что если вы используете его только с одним аргументом, ASSERT0 внизу увидит пустой второй аргумент. Если вы вызовете его с двумя аргументами, ASSERT0 увидит только их.

Также вам следует подумать о том, чтобы обернуть свой внутренний макрос в do { ... } while(0). В противном случае вы можете столкнуться с проблемой «висячего else» и сбить пользователей с толку другими синтаксическими эффектами.

person Jens Gustedt    schedule 09.03.2019
comment
Спасибо за ответ, это более сложный вопрос. Он действительно работает, но мне нужно подумать об этом на секунду... Хороший ответ. - person Łukasz Przeniosło; 09.03.2019
comment
@Afshin ваше решение менее хакерское (также нельзя вставить более 2 параметров), в то время как в этом в целом меньше макросов. - person Łukasz Przeniosło; 09.03.2019