Suppose I am maintaining a library function that takes two arguments, both pointers. The second argument exists only for backward compatibility; callers should always pass a NULL. I would like to put something into my header file that makes the compiler issue warnings if the second argument is not a compile-time constant NULL. I thought I would be able to do this using GCC's __builtin_constant_p
and __attribute__((warning))
extensions:
extern void thefun_called_with_nonnull_arg (void)
__attribute__((__warning__(
"'thefun' called with second argument not NULL")));
extern int real_thefun (void *, void *);
static inline int
thefun (void *a, void *b)
{
if (!__builtin_constant_p(b) || b != 0)
thefun_called_with_nonnull_arg();
return real_thefun(a, b);
}
int warning_expected (void *a, void *b)
{
return thefun(a, b);
}
int warning_not_expected (void *a)
{
return thefun(a, 0);
}
But this doesn't work with any version of GCC I have tested. I get warnings for both calls to thefun
. (Compiler Explorer demo.)
Can anyone suggest an alternative construct that will produce a warning for warning_expected
, and not for warning_not_expected
?
Notes:
b
is an int
.attribute((warning))
and I haven't had any luck finding an alternative.)b
is an int
and thefun
is marked always-inline.)thefun
as a macro would be preferable to one that does.-Werror
or equivalent is active.EDIT: Based on Kamil Cuk's discovery that the unwanted warning can be suppressed by casting the pointer to an integer of a different size, I have determined that this is an oversight in the implementation of __builtin_constant_p
and filed GCC bug report #91554. I'd still be interested in answers that provide ways to do this with clang, icc, or any other compiler that's commonly used together with GNU libc.
I finally managed to get it to work:
if (!__builtin_constant_p((int)(uintptr_t)b) || b != 0) {
With this you get only one warning.
It seems that gcc
can't do __builtin_constant_p
on a pointer type. The __builtin_constant_p(b)
always returns 0, so the warn function is always linked. Casting b
to int
strangely works. Although it looses precision in the pointer value, we don't care about it, cause we only check if it's a constant.
There is no way to do what you describe without GNU extensions.
This portable approach gives a hard error (because _Static_assert
requires a constant expression):
#define thefun(a, b) \
({ \
_Static_assert(b == 0, \
"'thefun' called with second argument not NULL"); \
real_thefun(a, b); \
})
However, there is one fortified-style approach that works on both GCC and Clang:
extern void thefun_called_with_nonnull_arg (void)
__attribute__((__deprecated__(
"'thefun' called with second argument not NULL")));
extern int real_thefun (void *, void *);
static inline int
thefun (void *a, void *b)
{
if (!__builtin_constant_p((unsigned short)(unsigned long)b) || b != 0)
thefun_called_with_nonnull_arg();
return real_thefun(a, b);
}
int warning_expected (void *a, void *b)
{
return thefun(a, b);
}
int warning_not_expected (void *a)
{
return thefun(a, 0);
}
Tested with GCC 8.3.0 and Clang 8.0.0.
See GCC bug report #91554 for more information about the need for the casts.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With