C & C++ DISCUSSION

Why does comparing ~x with a uint8_t fail in C? Integer promotion on small types

Started by kamulegeyadisan integer promotionuint8_t arithmeticbitwise complementusual arithmetic conversionssigned overflow
5 replies 248 views 6 participants
Latest activity · 30 Sep 2026

Why does comparing ~x with a uint8_t fail in C? Integer promotion on small types

kamulegeyadisan C & C++ Forum
#1

I store a byte and its complement as a simple integrity check: uint8_t a = 0x5A; uint8_t b = 0xA5;. The test if (~a == b) is never true, although ~0x5A is 0xA5 on paper. A second puzzle in the same project: with uint8_t x = 200, y = 100; the test if (x + y > 255) is true, even though I expected the sum to wrap to 44 in an 8-bit type.

What rule is being applied here, and how should I write these expressions so that they behave the same on an 8-bit AVR and a 32-bit ARM?

Community replies 5

Re: Why does comparing ~x with a uint8_t fail in C? Integer promotion on small types

#2

C never does arithmetic on types narrower than int. Before almost any operator is applied, operands of type char, short, uint8_t and similar undergo integer promotion: if int can represent every value of the type, the operand becomes an int, otherwise an unsigned int.

So ~a is not an 8-bit operation. a becomes the int 0x0000005A and its complement is 0xFFFFFFA5, which is -91. b is promoted to 165, and -91 is not 165. On an AVR with a 16-bit int the values are 0xFFA5 and 0x00A5, with the same outcome.

Re: Why does comparing ~x with a uint8_t fail in C? Integer promotion on small types

#3

The fix is to bring the result back to the narrow type: if ((uint8_t)~a == b), or mask it with if ((~a & 0xFF) == b).

Shifts behave the same way. With a uint8_t a, (a << 4) >> 4 does not clear the top nibble, because the intermediate value is an int that keeps bits 8 to 11. Write (uint8_t)(a << 4) >> 4 or simply a & 0x0F. Your sum is the same story: x + y is computed as the int 300, so the comparison with 255 is true. The wrap to 44 only happens when the result is stored back into a uint8_t.

Re: Why does comparing ~x with a uint8_t fail in C? Integer promotion on small types

#4

After promotion comes a second step, the usual arithmetic conversions, and that is where signed/unsigned surprises come from. If one operand is unsigned int and the other is int, the int is converted to unsigned. So with int n = -1; unsigned int len = 10; the test n < len is false, because -1 becomes 4294967295 with a 32-bit int.

With uint8_t len the same comparison is true, since len is promoted to int and the comparison is signed. Switch on -Wsign-compare (part of -Wextra for C in GCC) to have these flagged.

Re: Why does comparing ~x with a uint8_t fail in C? Integer promotion on small types

#5

A portability trap with 16-bit values: uint16_t a = 50000, b = 50000; uint32_t p = a * b;. On a 32-bit target both operands are promoted to signed int, the product 2,500,000,000 exceeds INT_MAX (2,147,483,647), and signed overflow is undefined behaviour even though every variable in sight is unsigned.

On a target with a 16-bit int, such as AVR, the multiplication is done in unsigned int and wraps, so p only receives the low 16 bits of the product. Force the width you want by casting one operand: uint32_t p = (uint32_t)a * b;.

Re: Why does comparing ~x with a uint8_t fail in C? Integer promotion on small types

#6

Constants have types too. A plain 1 is an int, so 1 << 31 shifts into the sign bit, which is undefined in C, and 1 << 16 is undefined on a 16-bit-int AVR because the shift count is not smaller than the width of the type. Build masks with 1u << n, 1UL << n or UINT32_C(1) << n, and use 1ULL for 64-bit masks.

MISRA C's essential type rules exist largely to stop code relying on these implicit conversions. Even outside MISRA projects, casting the result of ~ and << on small unsigned types back to that type is a habit worth adopting.

TEP COMMUNITY