4
votes

I am trying to work out how unsigned overflow works with subtraction, so I wrote the following test to try it out:

#include<stdio.h>
#include<stdlib.h>

unsigned char minWrap(unsigned char a, unsigned char b) {
    return a > b ? a - b : a + (0xff - b) + 1;
}

int main(int argc, char *argv[]) {
    unsigned char a = 0x01, b = 0xff;
    unsigned char c = a - b;

    printf("0x%02x 0x%02x 0x%02x\n", a-b, c, minWrap(a,b));

    return EXIT_SUCCESS;
}

Which gave as output:

0xffffff02 0x02 0x02

I would have expected the output to be the same three times. My question is: is it always safe to add/subtract unsigned chars and expect them to wrap around at 0xff?

Or more general, is it safe to compute with uintN_t and expect the result to be modulo 2^N?

3
Language lawyer: unsigned types don't overflow. - Kerrek SB

3 Answers

2
votes

is it always safe to add/subtract unsigned chars and expect them to wrap around at 0xff?

No. In C, objects of type char go through the usual integer promotions. So if the range of char fits in int (usual), it is converts to int, else unsigned.

a - b --> 0x01 - 0xFF --> 1 - 255 --> -254.

The below is undefined behavior as %x does not match an int and the value of -254 is not in the unsigned range (See @EOF comment). A typical behavior is a conversion to unsigned

printf("0x%02x\n", a-b);
// 0xffffff02

it safe to compute with uintN_t and expect the result to be modulo 2^N?

Yes. But be sure to make the result of type uintN_t and avoid unexpected usual integer promotions.

#include <inttypes.h>

uint8_t a = 0x01, b = 0xff;
uint8_t diff = a - b;

printf("0x%02x\n", (unsigned) diff);
printf("0x%02" PRTx8 "\n", diff);
1
votes

a-b in the printf line is evaluated after a and b are promoted to int. Also, the value is being treated as an unsigned int due to the use of %x in the format specifier by your run time environment.

It's equivalent to:

int a1 = a;
int b1 = b;
int x = a1 - b1;
printf("0x%02x 0x%02x 0x%02x\n", x, c, minWrap(a,b));

Section 6.3.1.8 Usual arithmetic conversions of the C99 standard has more details.

In theory use of an int when an unsigned int is expected in printf is cause for undefined behavior. A lenient run time environment, like you have, treats the int as an unsigned int and proceeds to print the value.

1
votes

From 6.3.1.8/1 of the standard concerning integer conversions:

The integer promotions are performed on both operands. Then the following rules are applied to the promoted operands

If both operands have the same type, then no further conversion is needed.

Otherwise, if both operands have signed integer types or both have unsigned integer types, the operand with the type of lesser integer conversion rank is converted to the type of the operand with greater rank.

Otherwise, if the operand that has unsigned integer type has rank greater or equal to the rank of the type of the other operand, then the operand with signed integer type is converted to the type of the operand with unsigned integer type.

Otherwise, if the type of the operand with signed integer type can represent all of the values of the type of the operand with unsigned integer type, then the operand with unsigned integer type is converted to the type of the operand with signed integer type.

Otherwise, both operands are converted to the unsigned integer type corresponding to the type of the operand with signed integer type.

In this case, the wrap-around is well defined. In the expression a-b, because both operands are of type unsigned char, they are first promoted to int and the operation is performed. If this value was assigned to an unsigned char, it would be properly truncated. However, you're passing this value to printf with a %x format specifier which expects an unsigned int. To display it correctly, use %hhx which expects an unsigned char.