Let's assume I have a macro (more details about why, is below in the P.S. section)
void my_macro_impl(uint32_t arg0, uint32_t arg1, uint32_t arg2);
...
#define MY_MACRO(arg0, arg1, arg2) my_macro_impl((uint32_t)(arg0), (uint32_t)(arg1), (uint32_t)(arg2))
The HW on which this macro is going to be used is little endian and uses 32bit architecture so that all the pointers are up to (and including) 32 bit width. My goal is to warn the user when it passes uint64_t
or int64_t
parameter by mistake.
I was thinking about using sizeof
like this
#define MY_MACRO(arg0, arg1, arg2) do \
{ \
static_assert(sizeof(arg0) <= sizeof(uint32_t)); \
static_assert(sizeof(arg1) <= sizeof(uint32_t)); \
static_assert(sizeof(arg2) <= sizeof(uint32_t)); \
my_macro_impl((uint32_t)(arg0), (uint32_t)(arg1), (uint32_t)(arg2)); \
} while (0)
But the user can use MY_MACRO
with a bit-field and then my code fails to compile:
error: invalid application of 'sizeof' to bit-field
Question: Is there an option to detect at the compilation time if the size of the macro argument larger than, let's say, uint32_t
?
P.S.
The MY_MACRO
is going to act similarly to printf
in a real-time embedded environment. This environment has a HW logger which may receive up to 5 parameters, each parameter should be 32 bits. The goal is to preserve the standard format as for printf
. The format strings are parsed offline and the parser is well aware that every parameter is 32 bits, so it will cast it based on the %...
from the format string. Possible usages are below.
Not desired usage:
uint64_t time = systime_get();
MY_MACRO_2("Starting execution at systime %llx", time); // WRONG! only the low 32 bits are printed. I want to detect it and fail the compilation.
Expected usage:
uint64_t time = systime_get();
MY_MACRO_3("Starting execution at systime %x%x", (uint32_t)(time >> 32), (uint32_t)time); // OK!
The following approach may work for this need:
#define CHECK_ARG(arg) _Generic((arg), \
int64_t : (arg), \
uint64_t : (arg), \
default : (uint32_t)(arg))
Then, the MY_MACRO
can be defined as
#define MY_MACRO(a0, a1, a2) do \
{ \
uint32_t arg1 = CHECK_ARG(a0); \
uint32_t arg2 = CHECK_ARG(a1); \
uint32_t arg3 = CHECK_ARG(a2); \
my_macro_impl(arg1, arg2, arg3);\
} while (0)
In such case, when passing for example uint64_t
, a warning is fired:
warning: implicit conversion loses integer precision: 'uint64_t' (aka 'unsigned long long') to 'uint32_t' (aka 'unsigned int') [-Wshorten-64-to-32]
Note:
Other types like double
, 128/256 bit types can be handled similarly.
Appropriate warnings should be enabled.
EDIT:
Inspired by Lundin's comment and answer, the proposed above solution can easily be modified to a portable version which will cause compilation error and not just a compiler warning.
#define CHECK_ARG(arg) _Generic((arg), \
int64_t : 0, \
uint64_t : 0, \
default : 1)
So the MY_MACRO
can be modified to
#define MY_MACRO(a0, a1, a2) do \
{ \
_Static_assert(CHECK_ARG(a1) && \
CHECK_ARG(a2) && \
CHECK_ARG(a3), \
"64 bit parameters are not supported!"); \
my_macro_impl((uint32_t)(a1), (uint32_t)(a2), (uint32_t)(a3)); \
} while (0)
This time, when passing uint64_t
parameter MY_MACRO(1ULL, 0, -1)
, the compilation fails with error:
error: static_assert failed due to requirement '_Generic((1ULL), long long: 0, unsigned long long: 0, default: 1) && (_Generic((0), long long: 0, unsigned long long: 0, default: 1) && _Generic((-1), long long: 0, unsigned long long: 0, default: 1))' "64 bit parameters are not supported!"
The type of the ternary ?:
expression is the common type of its second and third arguments (with integer promotion of smaller types). So the following version of your MY_MACRO
will work in a 32-bit architecture:
static_assert(sizeof(uint32_t) == sizeof 0, ""); // sanity check, for your machine
#define MY_MACRO(arg0, arg1, arg2) \
do { \
static_assert(sizeof(0 ? 0 : (arg0)) == sizeof 0, ""); \
static_assert(sizeof(0 ? 0 : (arg1)) == sizeof 0, ""); \
static_assert(sizeof(0 ? 0 : (arg2)) == sizeof 0, ""); \
my_macro_impl((uint32_t)(arg0), (uint32_t)(arg1), (uint32_t)(arg2)); \
} while (0)
Moreover, this solution should work with all versions of C and C++ (with, if necessary, a suitable definition of static_assert
).
Note this macro, like the OP's original, has function semantics in that the arguments are evaluated only once, unlike for example the notorious MAX
macro.
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