4

I am writing in C using the Atmel Studio (AVR-C.)

I have an if statement:

if( (rxProcessing < (rxWritePos-1) ) )

Where rxProcessing and rxWritePos are already type uint8_t . and I want the result of (rxWritePos-1) to be an unsigned 8-bit int (so that for example if rxWritePos is 0 then 0-1 = 255.)

I want to be sure that the result of the subtraction there will always be an 8-bit unsigned. How can I convert (or make sure that) the result of (rxWritePos-1) will always be an unsigned 8-bit int?

I think this will do:

if( (rxProcessing < ((uint8_t)rxWritePos-1) ) )

Note that , and I know that I can convert/cast a variable to uint8_t like this: uint8_t y = (uint8_t) x; but I want to make sure that the conversion will take place inside of if().

JRE
  • 67,678
  • 8
  • 104
  • 179
  • 1
    just like variables you can cast it like : (uint8_t)(rxWritePos-1) ... but you actually dont need to in your case.. – Hasan alattar Aug 18 '21 at 08:30
  • 5
    This question is software-related and belongs on stackoverflow – Jason S Aug 18 '21 at 19:19
  • @JasonS Stack overflow is so toxic, I avoid post questions there. Anyway since I use AVR-C for Atmel MCU, I thought I would ask here. – Christianidis Vasileios Aug 19 '21 at 06:44
  • 2
    @JasonS It is fine here, since there is a microcontroller programming aspect to the question - namely how to write ideal code for AVR, which is a sluggish 8 bitter. You can't just write code for those like any generic C. Partially because code efficiency on 8-bitters is next to non-existent, partially because implicit promotion is a nightmare on targets smaller than 32 bits. – Lundin Aug 19 '21 at 14:16

3 Answers3

10

rxWritePos is already uint8_t so obviously the cast (uint8_t)rxWritePos is nonsense - you are casting it to the type which it already has. Similarly, "do a lot of calculations then cast to the intended type" is risky. There could be unintended things in the calculation itself.

The issue here is mainly implicit type promotion. Operands of binary operators typically get promoted to int, which is a signed type. We almost never want to have any signed types in embedded systems, so that's problematic.

Furthermore the integer constant 1 is also of type int which is signed. As it happens, writing it as 1u and enforcing it to be unsigned int would have solved a lot of problems.

Apart from signedness, on any old 8 bit MCU we want to avoid 16 bit arithmetic when possible. So if there's accidental signedness changes because of implicit promotion, these may also block optimization and force the compiler to do the calculation in 16 bits. Because it can't know if you wrote the code counting on the implicit promotion to happen or not.

However, the correct/most robust solution to your problem is probably do the calculation in several steps with a temporary variable.

uint8_t writePos = rxWritePos;
writePos--;
if(rxProcessing < writePos)

Here writePos-- is guaranteed not to get promoted and it will use unsigned 8 bit arithmetic. Unsigned integers cannot over-/underflow but instead have a well-defined wrap-around from 255 to 0, which is what you want.

Lundin
  • 17,577
  • 1
  • 24
  • 67
9

You need parentheses around the whole expression: (uint8_t)(rxWritePos-1).

If rxWritePos = 0:

  • rxWritePos - 1 = −1
  • rxWritePos - 1u = some large number (unsigned wrap around with the type of 1u)
  • (uint8_t)(rxWritePos-1) = (uint8_t)(-1) = 255.

This is all governed by the type promotion rules of C, which can be a bit unintuitive. Better be obvious.

Now, when you say you want to the conversion to happen inside the if statement, this is a valid question from the point of view of finding the correct syntax. But this will most likely compile to the exact same machine code as declaring a new variable just before the if statement, which can be clearer (re: better be obvious).

user3840170
  • 125
  • 5
DamienD
  • 3,093
  • 1
  • 14
  • 23
  • Is the third point really defined behavior? I thought wrap-around is only defined for unsigned integers? Though on most CPUs this will be the behavior you see. – Michael Aug 19 '21 at 19:41
  • @Michael Yes it really is well-defined. See https://stackoverflow.com/questions/7221409/is-unsigned-integer-subtraction-defined-behavior. – Marco Aug 20 '21 at 04:28
  • 1
    Ooops, silly me, for a signed 0 - 1 there is no over or underflow :D – Michael Aug 20 '21 at 06:17
3
(uint8_t)((rxWritePos-1) & 255)

At each calculation you have to apply a mask, then you will get always the correct result.

Mike
  • 2,146
  • 1
  • 14
  • 29
Marko Buršič
  • 23,562
  • 2
  • 20
  • 33