Fixed-Width Types & Integer Promotion
Why embedded code uses uint8_t and uint32_t instead of int and char, and the promotion rules that silently change your arithmetic.
On a PC, int is 32 bits and nobody thinks about it. On an 8-bit AVR it is 16 bits; on a Cortex-M it is 32. Registers, packet fields and sensor readings have exact sizes, so embedded code states sizes explicitly with the types from <stdint.h>.
Printing and literals
if ((uint8_t)(a + b) > 255) Check before adding: if (a > UINT8_MAX - b) { /* would overflow */ }
A uint8_t can never exceed 255, so the cast makes the test always false. Test for overflow before it happens.
uint32_t mask = 1 << 31;
uint32_t mask = 1UL << 31;
1 is a signed int; shifting a 1 into its sign bit is undefined behaviour. The UL suffix makes the shift unsigned and at least 32 bits wide.
if (~flags8 == 0xF0) if ((uint8_t)~flags8 == 0xF0u) ~ is applied after promotion to int, so the result has 24 extra 1-bits and never equals 0xF0.
On a 16-bit MCU, what is sizeof(int)? And on a Cortex-M?
Typically 2 bytes on 16-bit (and 8-bit AVR) targets, 4 bytes on Cortex-M. That uncertainty is why register code uses uint16_t / uint32_t.
uint8_t x = 200; uint8_t y = x * 2; — what is y?
x * 2 is computed as int (400); storing it in uint8_t keeps the low 8 bits: 400 − 256 = 144.
When would you use uint_fast16_t instead of uint16_t?
For a variable that needs at least 16 bits but whose exact width does not matter (a loop counter); the compiler can pick the fastest width, e.g. 32 bits on Cortex-M, avoiding extra masking instructions.