The following code triggers a signed unsigned comparison warning:
uint16 x = 5;
if(((x) & (uint16)0x0001u) > 0u) {...}
Replacing the if with this eliminates the warning:
if(((x) & (uint16)0x0001u) > (uint8)0u) {...} //No warning
Shouldn't 0u become an unsigned integer and not trigger this warning to begin with?
Everything in C has a type and that includes integer constants such as 0
and 0u
.
In the original expression without any casts ((x & 0x0001u) > 0u)
:
Then x
is type uint16_t
, 0x0001u
is type unsigned int
(because of the u
) and 0u
is also unsigned int
.
From there, the usual arithmetic conversions which includes integer promotion apply (see Implicit type promotion rules). On a 32 or 64 bit system which this is (TriCore = 32 bit), the uint16_t
is integer promoted to int
, since uint16_t
is a small integer type (smaller than int
).
Then the left expression ends up with types int & unsigned int
. They are then balanced as per "the usual" by making the signed operand unsigned. The final expression ends up with types unsigned int > unsigned int
- the same types and an unsigned comparison.
In case of ((x) & (uint16)0x0001u) > 0u
, we end up with the same but now 0x0001u
is explicitly forced a conversion down to uint16_t
. We end up with types uint16_t & uint16_t
. Both operands are now integer promoted to int
, but since they are the same type, no further conversions are made. And so the type on the left side of >
is int
but on the right side it remains unsigned int
. And thus "signed vs unsigned comparison".
(Which is not necessarily an error, but could be. It's brittle and error-prone.)
In case of (x) & (uint16)0x0001u) > (uint8)0u
you force the operands further into different types: (uint16 & uint16_t) > uint8_t
. The left side is promoted to int
as before, but now uint8_t
is integer promoted to int
too. So you actually end up with a signed comparison even though all operands were unsigned types. Probably not the intention.
Good practices:
Avoid writing expressions that contain implicit type promotions as above, because they are subtle and error-prone.
Avoid casting whenever possible, unless you know exactly what you are doing.
Although on a 32 bit MCU, casting every operand to uint32_t
/unsigned int
removes all concerns about implicit promotions, so that's one sensible way to deal with it on that particular target.
When dealing with microcontrollers, MISRA C is highly recommended/industry standard. A large chapter in MISRA is dealing with implicit conversion bugs, so by enforcing the rules there you weed out such bugs.