C (XC8) implicit signed to unsigned conversion with ternary operator

Viewed 262

I am trying to set a color to either foreground or background (fg / bg) according to a mask.

uint8_t bitmap, mask, bg, fg = <whatever>;

This code works:

uint8_t color = bg;
if (bitmap & mask)
{
    color = fg;
}

This code works too but throws a warning: (373) implicit signed to unsigned conversion

uint8_t color = (bitmap & mask) ? fg : bg;

Can anyone explain why? I am using Microchip XC8 2.30.

2 Answers

The compiler warns you that the type of the ternary expression is int, a signed type, whereas the type of the assigned object is uint8_t, an unsigned type. Mixing signed and unsigned types in expressions can lead to counter intuitive behavior, such as sizeof(int) > -1 being false.

It could also warn you that storing an int value into a unt8_t variable may cause an implicit conversion that would change the value. But in this particular case, range analysis can easily prove that any possible outcome of this expression is in the range of type uint8_t so none of the above cases require a warning. This compiler is obnoxious and not smart enough.

The warning can be silenced with an extra cast:

uint8_t color = (uint8_t)((bitmap & mask) ? fg : bg);

but such useless casts obfuscate the code and confuse the reader. Cast should be avoided in most cases as they can lead to spurious bugs.

Your best solution seems to keep the if statement. The compiler might actually generate faster code for this solution as it is a good candidate for a conditional store instruction, producing efficient branchless code.

The conditional operator triggers the usual arithmetic conversions over its last two elements (in order to find an appropriate type for the expression as a whole). This in turns implies integer promotions for integral types of rank less than int, which the case here. Oddly, when all the values of the original type fits into an int, the expression gets promoted to int regardless of whether the original type was signed or not. Hence, your conditional expression ends up with type int, and back to uint8_t for the assignment.

This does not happen with the if statement, as in that case you have simple assignments in which both sides have the same uint8_t type.

Related