4
\$\begingroup\$

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().

\$\endgroup\$
4
  • 1
    \$\begingroup\$ just like variables you can cast it like : (uint8_t)(rxWritePos-1) ... but you actually dont need to in your case.. \$\endgroup\$ Aug 18, 2021 at 8:30
  • 5
    \$\begingroup\$ This question is software-related and belongs on stackoverflow \$\endgroup\$
    – Jason S
    Aug 18, 2021 at 19:19
  • \$\begingroup\$ @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. \$\endgroup\$ Aug 19, 2021 at 6:44
  • 2
    \$\begingroup\$ @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. \$\endgroup\$
    – Lundin
    Aug 19, 2021 at 14:16

3 Answers 3

9
\$\begingroup\$

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).

\$\endgroup\$
3
  • \$\begingroup\$ 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. \$\endgroup\$
    – Michael
    Aug 19, 2021 at 19:41
  • \$\begingroup\$ @Michael Yes it really is well-defined. See stackoverflow.com/questions/7221409/…. \$\endgroup\$
    – Marco
    Aug 20, 2021 at 4:28
  • 1
    \$\begingroup\$ Ooops, silly me, for a signed 0 - 1 there is no over or underflow :D \$\endgroup\$
    – Michael
    Aug 20, 2021 at 6:17
10
\$\begingroup\$

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.

\$\endgroup\$
3
\$\begingroup\$
(uint8_t)((rxWritePos-1) & 255)

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

\$\endgroup\$
3
  • 1
    \$\begingroup\$ Don't I still have to use the U prefix there? Like: 255U \$\endgroup\$ Aug 18, 2021 at 8:31
  • 1
    \$\begingroup\$ @ChristianidisVasileios I am not an C expert. I gave you an approach, but you have to apply the correct syntax. \$\endgroup\$ Aug 18, 2021 at 8:37
  • 1
    \$\begingroup\$ @ChristianidisVasileios Generally you should always use U suffix. In this specific case it doesn't seem to matter. In other cases it might. \$\endgroup\$
    – Lundin
    Aug 18, 2021 at 12:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.