2

I have 2 wrapper macros for asserting function input parameters:

/**
 * @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_;                                                        \

The former is used in functions returning void, while the latter in functions returning non-void. I was wondering either in C11 (or earlier) there is a mechanism allowing one to use only a single macro with variadic parameters amount. Depending on how many parameters are provided to the macro (1 or 2), a return or return ret_ would be compiled.

halfer
  • 19,824
  • 17
  • 99
  • 186
Łukasz Przeniosło
  • 2,725
  • 5
  • 38
  • 74
  • Smells like an "XY problem". What you probably should do is simply `assert(param1); assert(param2); ...`. How does it make sense to return from the function when an assert has triggered? – Lundin Mar 11 '19 at 13:57
  • @Lundin in a generic situation it doesnt make sense. But first- you should always handle all cases, so you always return after assert, no matter what. Second, this doesnt make sense only if the program is run in an environment controlled by an operating system, that will halt it at assert. In an embedded system, with no OS and many threads, bad things can happen when segmentation faults occur. Also depends on assert implementation, it doesnt always halt the thread. – Łukasz Przeniosło Mar 11 '19 at 14:02
  • In a real system you would always return an error code upon error; you wouldn't just silently crash in case of errors in a void function. So your scenario isn't realistic. You wouldn't have one function returning `void` and another returning `int`, but a single function type returning `err_t` or whatever. Also, bare metal systems should avoid assert() in the first place. – Lundin Mar 11 '19 at 14:06
  • @Lundin assert is a debugging tool. Its not even compiled in a release build. Its existence makes sense only for development purposes and is not taken under consideration in a release build. If it feels like it should, the functionalities are not build in a correct way. You are describing different mechanisms. – Łukasz Przeniosło Mar 11 '19 at 14:08
  • That's what I mean, in case of release build the assert wouldn't be there and so your void function would just silently return and the program have no way to tell that an error occurred. That's simply bad design. In addition, embedded systems would most often leave parameter validation to the caller, since it can be expensive. – Lundin Mar 11 '19 at 14:10
  • This topic is just too large to elaborate on. If you are commutating a motor in that function then yes, validation might be too much. But is it? Maybe the system is clocked fast enough, etc. Thats not the case. Those exemplary macros are simply missing #ifdef DEBUG statements, which I have in my code, but not here. On this you are right. – Łukasz Przeniosło Mar 11 '19 at 14:13
  • My point is that in a real system you would only have one result type `err_t` and so this whole topic doesn't make sense. And after you have implemented `err_t` there's no sense in separate handling in debug and release mode, since you will always detect errors no matter build. You can replace the macro with `if(param == 0) { return ERR_BAD_PARAM; }`. – Lundin Mar 11 '19 at 14:24
  • Obviously a system with only one return type would be great. But thats not available. Also, void returning functions certainly exist and thats not a bad practice, assuming they always work if input params are not null pointers. – Łukasz Przeniosło Mar 11 '19 at 14:27
  • Functions without error handling _are_ bad practice. So if you have a code base without error handling... that sucks. But you can turn that code base even worse by silently skip past errors, by silently returning from functions upon error. It would have been better practice to blink some LED, print some message, hang the program in a loop and wait for the watchdog etc etc. – Lundin Mar 11 '19 at 14:33
  • Thats what can be done in assert_param. Even if you get stuck in a forever loop there, you need a return statement for the sake of code correctness you mention so often. – Łukasz Przeniosło Mar 11 '19 at 14:38
  • Sorry, I'm not following... why would you need a return statement in a forever loop? – Lundin Mar 11 '19 at 14:41
  • In case you dont know either its there – Łukasz Przeniosło Mar 11 '19 at 14:49

2 Answers2

5

You can do it like this:

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

But remember, you cannot guarantee just 1 parameter in this variadic macro, so you need to use it correctly.

Update: Thanks to this thread, I came to better approach:

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);
}

This one is much better than previous one, because it somehow validates number of parameters.

Afshin
  • 8,839
  • 1
  • 18
  • 53
  • Damn, couldn't figure out this semantics, makes sense! And yes, I understand the safety drawbacks, like in all macros, but I really cant stand the overwhelming code copying. – Łukasz Przeniosło Mar 09 '19 at 08:00
  • 4
    Please note that this relies on a nonstandard extension that allows you to omit `...` arguments, and it'll generate warnings if you compile with `-pedantic`. (gcc/clang). – Petr Skocik Mar 09 '19 at 08:02
  • @Bremen I think you should be able to write better macro too. I have written the simplest form really. – Afshin Mar 09 '19 at 08:02
  • @Afshin this is a simple functionality, I cant think of more complex macro. Do you have anything specific mind? – Łukasz Przeniosło Mar 09 '19 at 08:04
  • @PSkocik yea, this is the simplest form. I have a exam right now,I will try to find better solution later. :) – Afshin Mar 09 '19 at 08:04
  • 1
    @Bremen I was thinking to use `__VA_OPT__` to expand a macro to 2 different ones, one 1 param and another with 2. So I think that way we should make errors if macro used incorrectly. But I should really think and try it myself. Although it will be C++2a. – Afshin Mar 09 '19 at 08:06
  • @Afshin isn't `__VA_OPT__` C++ only? – Łukasz Przeniosło Mar 09 '19 at 08:07
  • @Bremen yea it seems. But I have a feeling there is a workaround. I think I have seen something similar somewhere... – Afshin Mar 09 '19 at 08:08
  • @Bremen gcc manual says: `__VA_OPT__ is also available in GNU C and GNU C++. `. – Afshin Mar 09 '19 at 08:09
  • @Afshin Indeed, my gcc compiler supports `__VA_OPT__`, but I am not sure either it is possible to use it in this context. It seems to be dedicated to variadic functions usage, like the example shows in here https://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html – Łukasz Przeniosło Mar 09 '19 at 08:16
  • 1
    @Bremen Updated answer with better approach. – Afshin Mar 09 '19 at 09:02
3

There is a way to do such things that are standard C. First you have a core macro that does the job for both cases

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

As you can see this receives three arguments or more. And you'd have to arrange that RET is just the empty token for the case that you need this.

Now you can put a wrapper around that

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

This assures that commas that may be inside individual arguments will be seen as argument separators for ASSERT0.

The user level macro can then be

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

This ensures that if you use it with only one argument ASSERT0 down below will see an empty second argument. If you call it with two arguments, ASSERT0 will just see these.

Also you should consider to wrap your inner macro in do { ... } while(0). Otherwise you can have a "dangling else" problem and confuse users with other syntactic effects.

Jens Gustedt
  • 76,821
  • 6
  • 102
  • 177