3

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!

Amine Kchouk
  • 162
  • 1
  • 11
  • 1
    Things like 200, 2 and 3 are _signed_ integer constants. Does the behaviour change if you use 200u, 2u and 3u? – pmacfarlane Dec 20 '22 at 20:39
  • `I look at the map file that is generated` Did you consider looking at the generated assembly, not just the symbols? – KamilCuk Dec 20 '22 at 20:40
  • @pmacfarlane I didn't know about the "u" suffix! It does actually change the behavior and in all cases it only adds the unsigned routine thanks :) – Amine Kchouk Dec 20 '22 at 20:45
  • @KamilCuk So the thing is I know close to nothing when it comes to asm, but when I look at the generated file, in the case of `char` and `uint8_t`, I see only `__divmodhi4` being present. What does this mean? Is `__udivmodhi4` being added anyway when assembled or not? I do see that the binary file is larger so it seems to me that both are indeed added in the end... – Amine Kchouk Dec 20 '22 at 20:51
  • Glad to have helped, and that you learned something useful. I'm going to leave it for someone who understands the rules of integer promotion better than me to post an answer. I'm sure there are many. – pmacfarlane Dec 20 '22 at 20:53
  • 2
    Some good info is [here](https://stackoverflow.com/questions/46073295/implicit-type-promotion-rules) about what is going on. – pmacfarlane Dec 20 '22 at 21:02
  • @pmacfarlane Thanks for the link and more specifically thanks for actually putting a name on it "promotion"! – Amine Kchouk Dec 20 '22 at 21:04
  • This is actually one of the reasons why it's more tricky than people think to program 8 bit MCUs in C. Because C has this annoying tendency to convert everything to `int`, which is unsuitable both because it is unnecessary large and because it is signed. Sticking to `uint8_t`, `uint16_t` and `u` suffixed constants (`123u` instead of `123`) solves a lot of such problems. – Lundin Dec 21 '22 at 07:55

1 Answers1

3

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.
Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312