Using gcc version 4.8.4 on Linux, short is 16 bit and int is 32 bit.
#include "stdio.h"
int main( void ){
unsigned short u = 0xaabb;
unsigned int v = 0xaabb;
printf ("%08x %08x\n", u, (unsigned short)((u*0x10001)/0x100));
printf ("%08x %08x\n", v, (unsigned short)((v*0x10001)/0x100));
return 0;
}
Result:
0000aabb 0000bbab
0000aabb 0000bbaa
This can be varied, e.g., by dividing with 0x10, which produces a similar result (+1) for the first case. The effect does not occur if the byte truncated by /0x100
is less than 0x80. Machine code for the first case (short u
) looks as if some rounding (addition of 0xFF) is intended.
A literal like 0x10001
will be of type int
(if it can fit inside an int, which is true in this case). int
is a signed type.
Since the variable u
is a small integer type, it gets integer promoted to int
whenever used in an expression.
0xaabb * 0x10001
would supposedly give the result 0xAABBAABB
. However, that result is too large to fit inside an int
on a 32 bit two's complement system, where the largest number for an int
is 0x7FFFFFFF
. So you get an overflow on a signed integer and therefore invoke undefined behavior - anything can happen.
Never use signed integers when doing any form of binary arithmetic!
Furthermore, the final cast to (unsigned short)
is futile, because printf argument promotes the passed value to int
anyhow. Which is strictly speaking incorrect too, because %x
means that printf
expects an unsigned int
.
To avoid all trouble with the unpredictable and limited default integer types in C, use stdint.h
instead. Also, using unsigned int literals solves a lot of implicit type promotion bugs.
Example:
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
int main( void ){
uint16_t u = 0xaabb;
uint16_t v = 0xaabb;
printf ("%08" PRIx16 " %08" PRIx16 "\n", u, (uint16_t)(u*0x10001u/0x100u));
printf ("%08" PRIx16 " %08" PRIx16 "\n", v, (uint16_t)(v*0x10001u/0x100u));
return 0;
}
(This code will have argument promotion too, but by using the PRIx16
format specifier, you tell printf
that it is now the compiler's business to make the code work, regardless of what type promotions that might be present in the function call.)
Usual arithmetic conversions at play.
u
is converted to int
before multiplication. Since int
is signed it behaves differently on division.
printf("%08x\n", (u*0x10001)/0x100);
printf("%08x\n", (v*0x10001)/0x100);
Returns
ffaabbab
00aabbaa
Strictly speaking multiplication overflow on signed integer is already undefined behaviour, so result is invalid even before the division.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With