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.