Reputation: 103
Is there a way in C to force a function caller to send a constant value (expect compilation error if not) to the function?
for example, consider this simple function:
int foo(int x, int y)
{
assert( x < 10);
return x+y;
}
if I can somehow tell the compiler that x should be sent with a constant value in all cases, the assert can be evaluated at compile time (even if the assert is actually called only in run time). Is there a way to do so?
Just to explain the motivation - in low resources systems having things evaluated at compile time can lower SW footprint and execution time significantly.
Thanks.
Upvotes: 4
Views: 444
Reputation: 170153
First and foremost, here it is, a fully standard C11 solution:
#include <stdint.h>
#include <assert.h>
#define is_integral_constant_p(c) (_Generic(0 ? (void*)(uintptr_t)((c) - (c)) : (int*)(0), \
int*: 1, \
default: 0))
#define foo(x, y) (is_integral_constant_p(x) ? real_foo : error_foo_x_is_not_a_constant_expression)(x, y)
extern int error_foo_x_is_not_a_constant_expression(int, int);
int real_foo(int x, int y)
{
assert(x < 10);
return x+y;
}
int main(void)
{
static_assert(is_integral_constant_p(5), "5 is not a constant expression?");
foo(5, 1);
// int x = 0;
// foo(x, 1); // This will not link
}
The magic is in how the conditional expression works. If one of the operands is a null pointer constant, the type of the result is the type of the other pointer argument. Otherwise, if one argument is a void*
the result is void*
. Since (void*)(0)
is a null pointer constant, the second operand is a null pointer constant only when (c) - (c)
is an integral constant expression equal to 0
. Which happens iff c
is an integral constant expression.
The _Generic
just selects a constant based on the type of the result, which we expect to be either int*
in the true case, or void*
otherwise. What's more the macro itself evaluates to an integral constant expression (and so could even be used in an assert
).
The above is an adaptation of a technique used in the linux kernel (as discussed here), only without and GNU specific extension to C. Only C11 features are used.
Now, we implement foo
as a macro that does our check on x
, and either forwards to the "real_foo" that does the calculation, or to a function that is declared but not defined. So the program fails to link if x
is not a constant.
See it live here.
As for your particular case, you can remove the assertion entirely and move the check into the foo
macro. It would look something like this:
#define foo(x, y) (is_integral_constat_p(x) && (x < 10) ? real_foo : \
error_foo_x_is_not_a_constant_expression_or_more_than_10)(x, y)
The macro, combined with short circuit evaluation gets us the behavior you want. As you can see here.
At this point I feel I should caution you. This code may not be so straight-forward to protect in code review. So think carefully about whether or not any performance gain you may get is worth the explanations.
Upvotes: 5