Different compiler behavior when dividing unsigned char vs dividing unsigned short

105 views Asked by At

I'm not very experienced in the world of C programming, I just worked with arduino before, and I decided to get my feet more wet by doing a project on an ATtiny13 without using the Arduino IDE, only avr-gcc via cli. Since Flash storage is at a premium, I am exploring various techniques to reduce code size. In my journey I came across multiplications and divisions, and I just became aware of the existance of libgcc with its various arithmetic routines.

So I have this small test program:

unsigned char x = 200;

void main() {
    x = x*2/3;
}

If I look at the map file that is generated, I see that the compiler is adding both __udivmodhi4 and __divmodhi4 to do this calculation, of which the signed division routine __divmodhi4 seems unnecessary. However, if use a short instead of a char, as such:

unsigned short x = 200;

void main() {
    x = x*2/3;
}

This time I see only __udivmodhi4 being added in the map file, which is what I would expect. I also tried with uint8_t from inttypes.h and in that case it is adding back the signed division routine.

Could anyone please explain what is going on here? In all the above-mentioned cases, I would have expected the compiler to only add the unsigned division routine. Thanks!

1

There are 1 answers

0
Eric Postpischil On

Apparently in the C environment you are using, int is 16 bits. Then, when x is an unsigned char, in the expression x*2/3:

  • x is promoted to an int, as part of the usual arithmetic conversions applied for the operands of x*2.
  • Then, in x*2, both operands have type int, so no further promotions or conversions are needed, and the multiplication is performed with int arithmetic.
  • Similarly, both operands of / are int, so the division is performed with int arithmetic.

When x is an unsigned short:

  • unsigned short is the same width as unsigned int, so x is not eligible for promotion to int (promotion can only be to a type that can represent all values of the source type). It is promoted to unsigned int (which is the same width as unsigned short but is different for purposes of applying rules about types).
  • In x*2, x is an unsigned int and 2 is an int, so the usual arithmetic conversions convert 2 to an unsigned int, and the multiplication is performed using unsigned int arithmetic.
  • Similarly, for the division, 3 is converted to an unsigned int, and the division is performed using unsigned int arithmetic.

To keep the arithmetic unsigned when x is an unsigned char, you can use any of the following expressions, among other options:

  • (unsigned int) x * 2 / 3.
  • x * 2u / 3.
  • x * 2u / 3u.