-1

I am checking the input arguments provided and if it is not in the range specified, it should exit from the function. Suppose I have called a function, Func(arg). If arg is not in the specified range, the function should return to main().
The code sample is as follows:

#define check_param(expr) ((expr) ? (void)0U : exit(3))
void Func(int arg)
{
  check_param(arg);
  ...
  ...
}

int main()
{ 
  ...
  Func(10);
  ...
  return 0;
}

If I use exit(), it will exit the whole program. I want to return to main() and execute further instructions. I can't use return as it is a statement. What can be used in place of exit(3)?

The above definition is used for both type of functions (void and int type).

mod3k
  • 11
  • 3
  • 2
    Why are you writing the macro as an expression in the first place? Just write it as a statement (per above example). Did you think macros have to be expressions? – Eric Postpischil Aug 22 '22 at 11:47
  • @Aconcagua your extension gives a warning as `return without value` for function of return type int. But works perfectly with function of return type void. – mod3k Aug 22 '22 at 11:54
  • @EricPostpischil I will be using that in more than one function. So I have used the macro `#define` – mod3k Aug 22 '22 at 12:01
  • @mod3k Should it return to `main()`? Or should it exit the program? – Zakk Aug 22 '22 at 12:01
  • 1
    @mod3k: That does not answer the question. The question is not whether you are defining a macro so that it can be used in more than one place, the question is why are you defining the macro as an expression rather than as a statement. – Eric Postpischil Aug 22 '22 at 12:03
  • @Zakk it should return to `main()`. – mod3k Aug 22 '22 at 12:04
  • @Zakk: That is a sample of code they want to replace with different behavior as described in the question. – Eric Postpischil Aug 22 '22 at 12:07
  • 2
    "I can't use return as it is a statement." What is this supposed to mean? – Gerhardh Aug 22 '22 at 12:08
  • @Gerhardh: It means that replacing `exit(3)` with `return` in `((expr) ? (void)0U : exit(3))` will yield a compiler error, because only an expression can go in that place. The larger question is why OP thinks they need to be using an expression for the whole macro in the first place. – Eric Postpischil Aug 22 '22 at 12:10
  • @EricPostpischil, it is a code from a firmware project. The sample code is: `#define IS_DIO_INPUT( I_PIN ) (((I_PIN) == DI_1) || ((I_PIN) == DI_2) || ((I_PIN) == DI_3) || ((I_PIN) == DI_SW))`, where `DI_1`, `DI_2` are enum constants. and then using `check_param(IS_DIO_INPUT( x )` in a function, I am checking the parameter is one of the enum constants or not. – mod3k Aug 22 '22 at 12:16
  • @mod3k: Why cannot the macro replacement of `check_param(IS_DIO_INPUT( x ))` be a function, rather than an expression? You have not answered the question. The fact that `IS_DIO_INPUT` is defined as you show means it is an expression, but that does not prevent `check_parameter(x)` from being a statement. – Eric Postpischil Aug 22 '22 at 12:23
  • @mod3k That comment actually was a reply to an existing one – that got deleted, though, for the same reason. Did so, too – well, tried at least, now seeing the comment still was there :( – Aconcagua Aug 22 '22 at 12:23
  • @EricPostpischil in that case it should probable be "I cannot continue to use that ternary operator in places where it does not make any sense." ;) – Gerhardh Aug 22 '22 at 12:24
  • @EricPostpischil Actually I saw such code format in some peripheral library header files. So I used it. In STM32 HAL drivers, they used such format. – mod3k Aug 22 '22 at 12:29
  • 1
    @mod3k: Then be aware there is no need to use such a format. Macros **are not** required to be expressions. They may be statements or fragments of statements or other portions and combinations of source code. – Eric Postpischil Aug 22 '22 at 12:31
  • This is really just an "XY question". What you actually want: input verification of parameters to some GPIO function etc. First of all, note that in a sound design, the responsibility to verify this lies on the _caller_ not on the _function_. And next thing, note that GPIO port masks typically just boil down to integer constant expressions, so they should be checked at _compile time_ not in _run-time_! You can check them with a static assert. So now the actual question is rather: "how to wrap a function call with a static assert in C". None of the posted answers are anywhere near applicable. – Lundin Aug 22 '22 at 13:01
  • 1
    Also, whenever you are faced with the design decision "I repeat this check a lot, should I avoid code repetition or should I write the code as simple as readable as possible", the correct answer is almost always _write it as simple as possible; do not descend into some obscure macro madness_. That is much much worse programming than writing code that repeats the same line a few times here and there, period. The problem you are trying to solve is trivial, but your proposed solution is needlessly complex and very far from sensible. – Lundin Aug 22 '22 at 13:04

3 Answers3

5

Do not hide the return, that's just asking for trouble. Simply replace this part:

#define check_param(expr) ((expr) ? (void)0U : exit(3))
void Func(int arg)
{
    check_param(arg);

with this:

// suggestion: return 0 on success, or error code
int Func(int arg)
{
    if (!arg) return 3; // error code

    ...
    return 0; // no error
}

Using funny macros does not make the code easier to read, and they can be a real head-ache when debugging.

hyde
  • 60,639
  • 21
  • 115
  • 176
  • Well... Fully agree, nothing more to say about. This answer made me delete my own version... – Aconcagua Aug 22 '22 at 13:58
  • Agree. But `return` has to be without 3 as it would give warning. – mod3k Aug 23 '22 at 04:25
  • @mod3k Ah, right. Changed to code. You state the requirement to return to main with error. Now there are a few different ways to do this, if the function *has* to return `void`, but you should maybe ask a new question, maybe "How to get error from a function which returns void?" or something – hyde Aug 23 '22 at 05:28
  • @mod3k Or, maybe you are looking for this: https://en.cppreference.com/w/c/program/atexit ? – hyde Aug 23 '22 at 05:30
1
  1. I do not see any use of such a macro (maybe to make the code more difficult to read)
  2. I would use statements, not expressions.

This Macro will work both for void (second macro parameter blank after comma) and non void functions. cond and retval can be expressions objects or constants. retval does not have to be in parentheses as it is normally in macros because there are no other operations in this statement.

#define check(cond, retval)  do{if(!(cond)) return retval;}while(0)

int foo(int x)
{
    check(x == 2, x * 2);
    
    /* ... */

    return something;
}

void bar(const char *s1, const char *s2)
{
    check(!strcmp(s1,s2), );
}

https://godbolt.org/z/hY9rKe4xs

Example more real usage:

int div(const int x, const y)
{
    check(y != 0, INT_MIN);

    return x / y;
}
0___________
  • 60,014
  • 4
  • 34
  • 74
  • Code without explanation is not a good answer. – Eric Postpischil Aug 22 '22 at 12:08
  • 2
    @EricPostpischil there is not too much to explain - it is so simple – 0___________ Aug 22 '22 at 12:12
  • An answer should explicitly state that `retval` may and should be left blank when used in a function returning `void` and should not be blank when used in other functions. It should explain why `retval` is not in parentheses in `return retval`, as that may not be obvious to people learning C who have been told that uses of macro parameters should be parenthesized. It is a deviation from convention that is important to document. It should also explain why the statement is in a `do … while` instead of just a bare `if` statement. – Eric Postpischil Aug 22 '22 at 12:20
1

You do not need to use only an expression as the replacement sequence for a macro. It can be a statement (or part of one), such as:

#define check_param(expr)    if (!(expr)) return

Since you want the macro to work in functions that return void and in other functions, we need to give it a way to have a matching return statement, one that either does or does not give a return value, as desired. We can do this with another parameter:

#define check_param(expr, value)    if (!(expr)) return value

Then the macro can be used:

check_param(arg,);       // In function that returns void, value is blank.
check_param(arg, -1);    // In other functions, value is not blank.

Note that in return value, value is not in parentheses. It is usual to enclose macro arguments in parentheses to avoid precedence issues, but that cannot work here because we need return value to work when value is blank, and return (); would cause a syntax error.

Finally, when defining a macro as a statement, there is an idiom to wrap it in a do-while statement so that it acts grammatically like an ordinary statement:

#define check_param(expr, value)    do if (!(expr)) return value; while (0)

Note that, in the original if form, if the macro invocation happens to be followed by an else, like this:

if (A)
    check_param(arg, value);
else
    MyRoutine(arg);

then the else would be associated with the if resulting from the check_param macro instead of with the if (A). By wrapping the macro in do … while, we prevent this sort of undesired interpretation.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
  • 1
    Hm... I'd rather join [hyde](https://stackoverflow.com/a/73445346/1312382)'s answer – still your explanations are great, though I'd consider them rather as *general* advice (not involving hidden returns). – Aconcagua Aug 22 '22 at 14:04