Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why are integer types promoted during addition in C?

So we had a field issue, and after days of debugging, narrowed down the problem to this particular bit of code, where the processing in a while loop wasn't happening :

// heavily redacted code
// numberA and numberB are both of uint16_t
// Important stuff happens in that while loop

while ( numberA + 1 == numberB )
{
    // some processing
}

This had run fine, until we hit the uint16 limit of 65535. Another bunch of print statements later, we discovered that numberA + 1 had a value of 65536, while numberB wrapped back to 0. This failed the check and no processing was done.

This got me curious, so I put together a quick C program (compiled with GCC 4.9.2) to check this:

#include <stdio.h>
#include <stdint.h>

int main()
{

    uint16_t numberA, numberB;
    numberA = 65535;
    numberB = numberA + 1;

    uint32_t numberC, numberD;
    numberC = 4294967295;
    numberD = numberC + 1;

    printf("numberA = %d\n", numberA + 1);
    printf("numberB = %d\n", numberB);

    printf("numberC = %d\n", numberC + 1);
    printf("numberD = %d\n", numberD);

    return 0;
}

And the result was :

numberA = 65536
numberB = 0
numberC = 0
numberD = 0

So it appears that the result of numberA + 1 was promoted to uint32_t. Is this intended by the C language ? Or is this some compiler / hardware oddity?

like image 367
peonicles Avatar asked Apr 23 '15 08:04

peonicles


People also ask

Why does C have integer promotion?

Some data types like char , short int take less number of bytes than int, these data types are automatically promoted to int or unsigned int when an operation is performed on them. This is called integer promotion. For example no arithmetic calculation happens on smaller types like char, short and enum.

What is integer promotion in C?

CProgrammingServer Side Programming. There are some data types which take less number of bytes than integer datatype such as char, short etc. If any operations are performed on them, they automatically get promoted to int. This is known as integer promotions.

How is data type promotion done in an expression in C?

Type promotion in C is a method to convert any variable from one datatype to another. C allows variables of different datatypes to be present in a single expression. There are different types of type conversions available in C. They are Implicit type conversion and Explicit type conversion.

What is operator promotion in C?

Manipulating Values in Your Programs Implicit conversions are also associated with operator promotion, which is the automatic conversion of an operator from one type to another. When you do basic arithmetic operations on two variables, they are converted to the same type before doing the math.


3 Answers

So it appears that the result of numberA + 1 was promoted to uint32_t

The operands of the addition were promoted to int before the addition took place, and the result of the addition is of the same type as the effective operands (int).

Indeed, if int is 32-bit wide on your compilation platform (meaning that the type that represents uint16_t has lower “conversion rank” than int), then numberA + 1 is computed as an int addition between 1 and a promoted numberA as part of the integer promotion rules, 6.3.1.1:2 in the C11 standard:

The following may be used in an expression wherever an int or unsigned int may be used: […] An object or expression with an integer type (other than int or unsigned int) whose integer conversion rank is less than or equal to the rank of int and unsigned int.

[…]

If an int can represent all values of the original type […], the value is converted to an int

In your case, unsigned short which is in all likelihood what uint16_t is defined as on your platform, has all its values representable as elements of int, so the unsigned short value numberA gets promoted to int when it occurs in an arithmetic operation.

like image 197
Pascal Cuoq Avatar answered Oct 02 '22 08:10

Pascal Cuoq


For arithmetic operators such as +, the usual arithmetic conversions are applied.

For integers, the first step of those conversions is called the integer promotions, and this promotes any value of type smaller than int to be an int.

The other steps don't apply to your example so I shall omit them for conciseness.

In the expression numberA + 1, the integer promotions are applied. 1 is already an int so it remains unchanged. numberA has type uint16_t which is narrower than int on your system, so numberA gets promoted to int.

The result of adding two ints is another int, and 65535 + 1 gives 65536 since you have 32-bit ints.

So your first printf outputs this result.

In the line:

numberB = numberA + 1;

the above logic still applies to the + operator, this is equivalent to:

numberB = 65536;

Since numberB has an unsigned type, uint16_t specifically, 65536 is reduced (mod 65536) which gives 0.

Note that your last two printf statements cause undefined behaviour; you must use %u for printing unsigned int. To cope with different sizes of int, you can use "%" PRIu32 to get the format specifier for uint32_t.

like image 23
M.M Avatar answered Oct 01 '22 08:10

M.M


When the C language was being developed, it was desirable to minimize the number of kinds of arithmetic compilers had to deal with. Thus, most math operators (e.g. addition) supported only int+int, long+long, and double+double. While the language could have been simplified by omitting int+int (promoting everything to long instead), arithmetic on long values generally takes 2-4 times as much code as arithmetic on int values; since most programs are dominated by arithmetic on int types, that would have been very costly. Promoting float to double, by contrast, will in many cases save code, because it means that only two functions are needed to support float: convert to double, and convert from double. All other floating-point arithmetic operations need only support one floating-point type, and since floating-point math is often done by calling library routines the cost of calling a routine to add two double values is often the same as the cost to call a routine to add two float values.

Unfortunately, the C language became widespread on a variety of platforms before anyone really figured out what 0xFFFF + 1 should mean, and by that time there were already some compilers where the expression yielded 65536 and some where it yielded zero. Consequently, writers of standards have endeavored to write them in a fashion that would allow compilers to keep on doing whatever they were doing, but which was rather unhelpful from the standpoint of anyone hoping to write portable code. Thus, on platforms where int is 32 bits, 0xFFFF+1 will yield 65536, and on platforms where int is 16 bits, it will yield zero. If on some platform int happened to be 17 bits, 0xFFFF+1 would authorize the compiler to negate the laws of time and causality [btw, I don't know if any 17-bit platforms, but there are some 32-bit platforms where uint16_t x=0xFFFF; uint16_t y=x*x; will cause the compiler to garble the behavior of code which precedes it].

like image 20
supercat Avatar answered Oct 02 '22 08:10

supercat