int promotion: Is the following well-defined? - c

Suppose that on a C implementation (e.g. on a x86 C compiler) USHRT_MAX = 65535 and INT_MAX = 2147483647. Is, then, the following statement well-defined?
unsigned short product = USHRT_MAX * USHRT_MAX;
According to the following in the C99 standard both operands are promoted to int (since int can represent all possible values of unsigned short) and, therefore, the result is not well-defined, since an overflow will occur (65535 ^ 2 = 4294836225 > 2147483647), which means that the value of product is not well-defined:
6.3.1.1-1
If an int can represent all values of the original type, the value is
converted to an int; otherwise, it is converted to an unsigned int.
These are called the integer promotions.(48) All other types are
unchanged by the integer promotions.
48) The integer promotions are applied only: as part of the usual
arithmetic conversions, to certain argument expressions, to the
operands of the unary +, -, and ~ operators, and to both operands of
the shift operators, as specified by their respective subclauses.
However, according to the following, the result is well-defined, since computations involving unsigned operands do not overflow:
6.2.5-9
The range of nonnegative values of a signed integer type is a subrange
of the corresponding unsigned integer type, and the representation of
the same value in each type is the same.(31) A computation involving
unsigned operands can never overflow, because a result that cannot be
represented by the resulting unsigned integer type is reduced modulo
the number that is one greater than the largest value that can be
represented by the resulting type.
Does the variable product in the aforementioned statement have a well-defined value?
EDIT: What should happen in the following case?
unsigned short lhs = USHRT_MAX;
unsigned short rhs = USHRT_MAX;
unsigned short product = lhs * rhs;

The promotion wins.
Says section 5.2.4.2.1 about the constants USHRT_MAX etc.:
The values given below shall be replaced by constant expressions suitable for use in #if preprocessing directives. Moreover, except for CHAR_BIT and MB_LEN_MAX, the following shall be replaced by expressions that have the same type as would an expression that is an object of the corresponding type converted according to the integer promotions.
So the multiplication is on ints, and involves no unsigned operands, unambiguously, there is no conforming way to implement USHRT_MAX to get an operation involving unsigned operands if USHRT_MAX < INT_MAX. Thus you have overflow, and undefined behaviour.
Regarding the added question
EDIT: What should happen in the following case?
unsigned short lhs = USHRT_MAX;
unsigned short rhs = USHRT_MAX;
unsigned short product = lhs * rhs;
That is exactly the same situation. The operands of * are subject to the integer promotions, all values of type unsigned short can be represented as ints by the assumption on the values of USHRT_MAX and INT_MAX, so the multiplication is on ints, and with the specified values overflows.
You need to convert at least one operand to an unsigned type that is not promoted to int in oerder to have the multiplication be performed on unsigned operands.

You get UB since by the time the multiplication operator is applied, its operands are already signed integers (because of the promotions to int occurring first).
You can work-around that with this:
unsigned short product = USHRT_MAX * (unsigned)USHRT_MAX;
Proof that (unsigned)some_integer stays unsigned:
#include <stdio.h>
int main(void)
{
printf("1u * (-1) = %f\n", (((unsigned)1) * (-1)) + 0.0);
printf("1 * (-1) = %f\n", (1 * (-1)) + 0.0);
return 0;
}
Output (ideone):
1u * (-1) = 4294967295.000000
1 * (-1) = -1.000000
Good catch, btw.

Related

Comparisons with bit shifting [duplicate]

This post is meant to be used as a FAQ regarding implicit integer promotion in C, particularly implicit promotion caused by the usual arithmetic conversions and/or the integer promotions.
Example 1)
Why does this give a strange, large integer number and not 255?
unsigned char x = 0;
unsigned char y = 1;
printf("%u\n", x - y);
Example 2)
Why does this give "-1 is larger than 0"?
unsigned int a = 1;
signed int b = -2;
if(a + b > 0)
puts("-1 is larger than 0");
Example 3)
Why does changing the type in the above example to short fix the problem?
unsigned short a = 1;
signed short b = -2;
if(a + b > 0)
puts("-1 is larger than 0"); // will not print
(These examples were intended for a 32 or 64 bit computer with 16 bit short.)
C was designed to implicitly and silently change the integer types of the operands used in expressions. There exist several cases where the language forces the compiler to either change the operands to a larger type, or to change their signedness.
The rationale behind this is to prevent accidental overflows during arithmetic, but also to allow operands with different signedness to co-exist in the same expression.
Unfortunately, the rules for implicit type promotion cause much more harm than good, to the point where they might be one of the biggest flaws in the C language. These rules are often not even known by the average C programmer and therefore cause all manner of very subtle bugs.
Typically you see scenarios where the programmer says "just cast to type x and it works" - but they don't know why. Or such bugs manifest themselves as rare, intermittent phenomena striking from within seemingly simple and straight-forward code. Implicit promotion is particularly troublesome in code doing bit manipulations, since most bit-wise operators in C come with poorly-defined behavior when given a signed operand.
Integer types and conversion rank
The integer types in C are char, short, int, long, long long and enum.
_Bool/bool is also treated as an integer type when it comes to type promotions.
All integers have a specified conversion rank. C11 6.3.1.1, emphasis mine on the most important parts:
Every integer type has an integer conversion rank defined as follows:
— No two signed integer types shall have the same rank, even if they have the same representation.
— The rank of a signed integer type shall be greater than the rank of any signed integer type with less precision.
— The rank of long long int shall be greater than the rank of long int, which shall be greater than the rank of int, which shall be greater than the rank of short int, which shall be greater than the rank of signed char.
— The rank of any unsigned integer type shall equal the rank of the corresponding signed integer type, if any.
— The rank of any standard integer type shall be greater than the rank of any extended integer type with the same width.
— The rank of char shall equal the rank of signed char and unsigned char.
— The rank of _Bool shall be less than the rank of all other standard integer types.
— The rank of any enumerated type shall equal the rank of the compatible integer type (see 6.7.2.2).
The types from stdint.h sort in here too, with the same rank as whatever type they happen to correspond to on the given system. For example, int32_t has the same rank as int on a 32 bit system.
Further, C11 6.3.1.1 specifies which types are regarded as the small integer types (not a formal term):
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.
What this somewhat cryptic text means in practice, is that _Bool, char and short (and also int8_t, uint8_t etc) are the "small integer types". These are treated in special ways and subject to implicit promotion, as explained below.
The integer promotions
Whenever a small integer type is used in an expression, it is implicitly converted to int which is always signed. This is known as the integer promotions or the integer promotion rule.
Formally, the rule says (C11 6.3.1.1):
If an int can represent all values of the original type (as restricted by the width, for a bit-field), the value is converted to an int; otherwise, it is converted to an unsigned int. These are called the integer promotions.
This means that all small integer types, no matter signedness, get implicitly converted to (signed) int when used in most expressions.
This text is often misunderstood as: "all small signed integer types are converted to signed int and all small, unsigned integer types are converted to unsigned int". This is incorrect. The unsigned part here only means that if we have for example an unsigned short operand, and int happens to have the same size as short on the given system, then the unsigned short operand is converted to unsigned int. As in, nothing of note really happens. But in case short is a smaller type than int, it is always converted to (signed) int, regardless of it the short was signed or unsigned!
The harsh reality caused by the integer promotions means that almost no operation in C can be carried out on small types like char or short. Operations are always carried out on int or larger types.
This might sound like nonsense, but luckily the compiler is allowed to optimize the code. For example, an expression containing two unsigned char operands would get the operands promoted to int and the operation carried out as int. But the compiler is allowed to optimize the expression to actually get carried out as an 8-bit operation, as would be expected. However, here comes the problem: the compiler is not allowed to optimize out the implicit change of signedness caused by the integer promotion because there is no way for the compiler to tell if the programmer is purposely relying on implicit promotion to happen, or if it is unintentional.
This is why example 1 in the question fails. Both unsigned char operands are promoted to type int, the operation is carried out on type int, and the result of x - y is of type int. Meaning that we get -1 instead of 255 which might have been expected. The compiler may generate machine code that executes the code with 8 bit instructions instead of int, but it may not optimize out the change of signedness. Meaning that we end up with a negative result, that in turn results in a weird number when printf("%u is invoked. Example 1 could be fixed by casting the result of the operation back to type unsigned char.
With the exception of a few special cases like ++ and sizeof operators, the integer promotions apply to almost all operations in C, no matter if unary, binary (or ternary) operators are used.
The usual arithmetic conversions
Whenever a binary operation (an operation with 2 operands) is done in C, both operands of the operator have to be of the same type. Therefore, in case the operands are of different types, C enforces an implicit conversion of one operand to the type of the other operand. The rules for how this is done are named the usual artihmetic conversions (sometimes informally referred to as "balancing"). These are specified in C11 6.3.18:
(Think of this rule as a long, nested if-else if statement and it might be easier to read :) )
6.3.1.8 Usual arithmetic conversions
Many operators that expect operands of arithmetic type cause conversions and yield result
types in a similar way. The purpose is to determine a common real type for the operands
and result. For the specified operands, each operand is converted, without change of type
domain, to a type whose corresponding real type is the common real type. Unless
explicitly stated otherwise, the common real type is also the corresponding real type of
the result, whose type domain is the type domain of the operands if they are the same,
and complex otherwise. This pattern is called the usual arithmetic conversions:
First, if the corresponding real type of either operand is long double, the other operand is converted, without change of type domain, to a type whose corresponding real type is long double.
Otherwise, if the corresponding real type of either operand is double, the other operand is converted, without change of type domain, to a type whose corresponding real type is double.
Otherwise, if the corresponding real type of either operand is float, the other operand is converted, without change of type domain, to a type whose corresponding real type is float.
Otherwise, 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.
Notable here is that the usual arithmetic conversions apply to both floating point and integer variables. In the case of integers, we can also note that the integer promotions are invoked from within the usual arithmetic conversions. And after that, when both operands have at least the rank of int, the operators are balanced to the same type, with the same signedness.
This is the reason why a + b in example 2 gives a strange result. Both operands are integers and they are at least of rank int, so the integer promotions do not apply. The operands are not of the same type - a is unsigned int and b is signed int. Therefore the operator b is temporarily converted to type unsigned int. During this conversion, it loses the sign information and ends up as a large value.
The reason why changing type to short in example 3 fixes the problem, is because short is a small integer type. Meaning that both operands are integer promoted to type int which is signed. After integer promotion, both operands have the same type (int), no further conversion is needed. And then the operation can be carried out on a signed type as expected.
According to the previous post, I want to give more information about each example.
Example 1)
int main(){
unsigned char x = 0;
unsigned char y = 1;
printf("%u\n", x - y);
printf("%d\n", x - y);
}
Since unsigned char is smaller than int, we apply the integer promotion on them, then we have (int)x-(int)y = (int)(-1) and unsigned int (-1) = 4294967295.
The output from the above code:(same as what we expected)
4294967295
-1
How to fix it?
I tried what the previous post recommended, but it doesn't really work.
Here is the code based on the previous post:
change one of them to unsigned int
int main(){
unsigned int x = 0;
unsigned char y = 1;
printf("%u\n", x - y);
printf("%d\n", x - y);
}
Since x is already an unsigned integer, we only apply the integer promotion to y. Then we get (unsigned int)x-(int)y. Since they still don't have the same type, we apply the usual arithmetic converions, we get (unsigned int)x-(unsigned int)y = 4294967295.
The output from the above code:(same as what we expected):
4294967295
-1
Similarly, the following code gets the same result:
int main(){
unsigned char x = 0;
unsigned int y = 1;
printf("%u\n", x - y);
printf("%d\n", x - y);
}
change both of them to unsigned int
int main(){
unsigned int x = 0;
unsigned int y = 1;
printf("%u\n", x - y);
printf("%d\n", x - y);
}
Since both of them are unsigned int, no integer promotion is needed. By the usual arithmetic converison(have the same type), (unsigned int)x-(unsigned int)y = 4294967295.
The output from the above code:(same as what we expected):
4294967295
-1
One of possible ways to fix the code:(add a type cast in the end)
int main(){
unsigned char x = 0;
unsigned char y = 1;
printf("%u\n", x - y);
printf("%d\n", x - y);
unsigned char z = x-y;
printf("%u\n", z);
}
The output from the above code:
4294967295
-1
255
Example 2)
int main(){
unsigned int a = 1;
signed int b = -2;
if(a + b > 0)
puts("-1 is larger than 0");
printf("%u\n", a+b);
}
Since both of them are integers, no integer promotion is needed. By the usual arithmetic conversion, we get (unsigned int)a+(unsigned int)b = 1+4294967294 = 4294967295.
The output from the above code:(same as what we expected)
-1 is larger than 0
4294967295
How to fix it?
int main(){
unsigned int a = 1;
signed int b = -2;
signed int c = a+b;
if(c < 0)
puts("-1 is smaller than 0");
printf("%d\n", c);
}
The output from the above code:
-1 is smaller than 0
-1
Example 3)
int main(){
unsigned short a = 1;
signed short b = -2;
if(a + b < 0)
puts("-1 is smaller than 0");
printf("%d\n", a+b);
}
The last example fixed the problem since a and b both converted to int due to the integer promotion.
The output from the above code:
-1 is smaller than 0
-1
If I got some concepts mixed up, please let me know. Thanks~
Integer and floating point rank and promotion rules in C and C++
I'd like to take a stab at this to summarize the rules so I can quickly reference them. I've fully studied the question and both of the other two answers here, including the main one by #Lundin. If you want more examples beyond the ones below, go study that answer in detail as well, while referencing my "rules" and "promotion flow" summaries below.
I've also written my own example and demo code here: integer_promotion_overflow_underflow_undefined_behavior.c.
Despite normally being incredibly verbose myself, I'm going to try to keep this a short summary, since the other two answers plus my test code already have sufficient detail via their necessary verbosity.
Integer and variable promotion quick reference guide and summary
3 simple rules
For any operation where multiple operands (input variables) are involved (ex: mathematical operations, comparisons, or ternary), the variables are promoted as required to the required variable type before the operation is performed.
Therefore, you must manually, explicitly cast the output to any desired type you desire if you do not want it to be implicitly chosen for you. See the example below.
All types smaller than int (int32_t on my 64-bit Linux system) are "small types". They cannot be used in ANY operation. So, if all input variables are "small types", they are ALL first promoted to int (int32_t on my 64-bit Linux system) before performing the operation.
Otherwise, if at least one of the input types is int or larger, the other, smaller input type or types are promoted to this largest-input-type's type.
Example
Example: with this code:
uint8_t x = 0;
uint8_t y = 1;
...if you do x - y, they first get implicitly promoted to int (which is int32_t on my 64-bit
system), and you end up with this: (int)x - (int)y, which results in an int type with value
-1, rather than a uint8_t type of value 255. To get the desired 255 result, manually
cast the result back to uint8_t, by doing this: (uint8_t)(x - y).
Promotion flow
The promotion rules are as follows. Promotion from smallest to largest types is as follows.
Read "-->" as "gets promoted to".
The types in square brackets (ex: [int8_t]) are the typical "fixed-width integer types" for the given standard type on a typical 64-bit Unix (Linux or Mac) architecture. See, for example:
https://www.cs.yale.edu/homes/aspnes/pinewiki/C(2f)IntegerTypes.html
https://www.ibm.com/docs/en/ibm-mq/7.5?topic=platforms-standard-data-types
And even better, test it for yourself on your machine by running my code here!: stdint_sizes.c from my eRCaGuy_hello_world repo.
1. For integer types
Note: "small types" = bool (_Bool), char [int8_t], unsigned char [uint8_t], short [int16_t], unsigned short [uint16_t].
SMALL TYPES: bool (_Bool), char [int8_t], unsigned char [uint8_t], short [int16_t], unsigned short [uint16_t]
--> int [int32_t]
--> unsigned int [uint32_t]
--> long int [int64_t]
--> unsigned long int [uint64_t]
--> long long int [int64_t]
--> unsigned long long int [uint64_t]
Pointers (ex: void*) and size_t are both 64-bits, so I imagine they fit into the uint64_t category above.
2. For floating point types
float [32-bits] --> double [64-bits] --> long double [128-bits]
I would like to add two clarifications to #Lundin's otherwise excellent answer, regarding example 1, where there are two operands of identical integer type, but are "small types" that require integer promotion.
I'm using the N1256 draft since I don't have access to a paid copy of the C standard.
First: (normative)
6.3.1.1's definition of integer promotion isn't the triggering clause of actually doing integer promotion. In reality it is 6.3.1.8 Usual arithmetic conversions.
Most of the time, the "usual arithmetic conversions" apply when the operands are of different types, in which case at least one operand must be promoted. But the catch is that for integer types, integer promotion is required in all cases.
[clauses of floating-point types come first]
Otherwise, 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.
Second: (non-normative)
There is an explicit example cited by the standard to demonstrate this:
EXAMPLE 2 In executing the fragment
char c1, c2;
/* ... */
c1 = c1 + c2;
the "integer promotions" require that the abstract machine promote the value of each variable to int size
and then add the two ints and truncate the sum. Provided the addition of two chars can be done without
overflow, or with overflow wrapping silently to produce the correct result, the actual execution need only
produce the same result, possibly omitting the promotions.

Why should be there the involvement of type promotion in this code?

In the code below the sizeof(int) will produce a signed int with a value of 4 bytes (suppose on a particular compiler) and -1 is also signed int, then my answer should be Yes but it displays No.
#include <stdio.h>
int main()
{
if (sizeof(int) > -1)
printf("Yes");
else
printf("No");
return 0;
}
Well it is the famous signed unsigned comparison. Here -1 which is signed number when compared to unsigned numbers - promoted to an unsigned number resulting in a big magnitude value (SIZE_MAX). So it is always false.
The explanation why there would be type promotion here when comparing:
From standard §6.3.1.8
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.
Also from §6.5.8 under relational operators
If both of the operands have arithmetic type, the usual arithmetic
conversions are performed.
And what sizeof returns?
Under The sizeof and _Alignof operators §6.5.3.4
The value of the result of both operators is implementation-defined, and its type (an unsigned integer type) is
size_t, defined in (and other headers).
And in section §7.19 under Common definitions
size_t which is the unsigned integer type of the result of the sizeof
operator;
To clarify a bit further when you are converting -1 to size_t it will have the value (Basically modulo SIZE_MAX+1)
SIZE_MAX+1+(-1)
Also from standard §6.2.5 (Explaining the conversion)
A computation involving unsigned operands can never overflow, because
a result that cannot be represented by the resulting unsigned integer
type is reduced modulo the number that is one greater than the largest
value that can be represented by the resulting type.

Cast from "int" to "unsigned short" after applying bitwise operator "~"

Static analysis tool I use raises a warning for this code :
uint16 var1 = 1U;
uint16 var2 = ~var1;
I check MISRA C 2004 rules and I find 10.5 rule :
If the bitwise operators ~ and << are applied to an operand od underlying type unsigned char or unsigned short, the result shall be immediately cast to the underlying type of the operand.
Ok, it's not a problem, implicit cast is applied (I think "cast" means implicit or explicit cast). But 10.1 rule says :
The value of an expression of integer type shall not be implicitly converted to a different underlying type the expression is complex.
An previous example of complex operation are : ~u16a
I change my code to :
uint16 var1 = 1U;
uint16 var2 = (uint16) ~var1;
And I obtain another warning : I think conversion of int negative value to unsigned int value not safe. I check C99 standard (ISO C99) § 6.3.1.3 but I don't understand if conversion of int to unsigned short are clearly specified.
In EmbeddedGurus article I read :
c = (unsigned int) a; /* Since a is positive, this cast is safe */
My questions :
Have explicit conversion from signed int to unsigned short unspecified behavior ?
If yes, how to use complement operator with unsigned short in safe way ?
The operands of the arithmetic and bitwise operators always undergo the standard promotions before the value is computed. Anything shorter than an int is promoted to either int or unsigned int, depending on the platform (i.e. depending on whether int can represent all values of the type that's being promoted).
On your platform, uint16_t is standard-promoted to int, since your int can represent all values of a uint16_t. Then the bitwise negation is applied to that int value, which is the cause of the problem.
To get a deterministic result independent of the platform, convert the value to an unsigned int yourself:
uint16_t var2 = (uint16_t) ~((unsigned int) var1);
Note that this is always correct, since unsigned int is required to be able to represent all the values of a uint16_t.
Have explicit conversion from signed int to unsigned short unspecified behavior ?
The conversion from signed to unsigned values is well specified it happens via modulo arithmetic is covered by section 6.3.1.3 Signed and unsigned integers from the C99 draft standard:
Otherwise, if the new type is unsigned, the value is converted by repeatedly adding or
subtracting one more than the maximum value that can be represented in the new type
until the value is in the range of the new type.49)
So for your case the negative number would be converted by repeatedly adding:
UMAX + 1
to the negative result until it is range of the unsigned type you are converting to.
For example the conversion of -1 to an unsigned type always results in the max unsigned value since -1 + UMAX + 1 is always UMAX.
If yes, how to use complement operator with unsigned short in safe way ?
What happens when you apply the ~ operator is that the value is bring promoted to int due to integer promotions being applied to the operand of ~. Which is covered in section 6.5.3.3 Unary arithmetic operators which says (emphasis mine):
The integer promotions are performed on the operand, and the result
has the promoted type. If the promoted type is an unsigned type, the
expression ~E is equivalent to the maximum value representable in that
type minus E.
Given the last sentence in the quoted paragraph perhaps casting to unsigned int first may lead to more intuitive results:
uint16 var2 = ~((unsigned int)var1);
and since you are required to apply an explicit cast then you end up with this:
uint16 var2 = (uint16) ~((unsigned int)var1);

Why are the results of integer promotion different?

Please look at my test code:
#include <stdlib.h>
#include <stdio.h>
#define PRINT_COMPARE_RESULT(a, b) \
if (a > b) { \
printf( #a " > " #b "\n"); \
} \
else if (a < b) { \
printf( #a " < " #b "\n"); \
} \
else { \
printf( #a " = " #b "\n" ); \
}
int main()
{
signed int a = -1;
unsigned int b = 2;
signed short c = -1;
unsigned short d = 2;
PRINT_COMPARE_RESULT(a,b);
PRINT_COMPARE_RESULT(c,d);
return 0;
}
The result is the following:
a > b
c < d
My platform is Linux, and my gcc version is 4.4.2.
I am surprised by the second line of output.
The first line of output is caused by integer promotion. But why is the result of the second line different?
The following rules are from C99 standard:
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.
I think both of the two comparisons should belong to the same case, the second case of integer promotion.
When you use an arithmetic operator, the operands go through two conversions.
Integer promotions: If int can represent all values of the type, then the operand is promoted to int. This applies to both short and unsigned short on most platforms. The conversion performed on this stage is done on each operand individually, without regard for the other operand. (There are more rules, but this is the one that applies.)
Usual arithmetic conversions: If you compare an unsigned int against a signed int, since neither includes the entire range of the other, and both have the same rank, then both are converted to the unsigned type. This conversion is done after examining the type of both operands.
Obviously, the "usual arithmetic conversions" don't always apply, if there are not two operands. This is why there are two sets of rules. One gotcha, for example, is that shift operators << and >> don't do usual arithmetic conversions, since the type of the result should only depend on the left operand (so if you see someone type x << 5U, then the U stands for "unnecessary").
Breakdown: Let's assume a typical system with 32-bit int and 16-bit short.
int a = -1; // "signed" is implied
unsigned b = 2; // "int" is implied
if (a < b)
puts("a < b"); // not printed
else
puts("a >= b"); // printed
First the two operands are promoted. Since both are int or unsigned int, no promotions are done.
Next, the two operands are converted to the same type. Since int can't represent all possible values of unsigned, and unsigned can't represent all possible values of int, there is no obvious choice. In this case, both are converted to unsigned.
When converting from signed to unsigned, 232 is repeatedly added to the signed value until it is in the range of the unsigned value. This is actually a noop as far as the processor is concerned.
So the comparison becomes if (4294967295u < 2u), which is false.
Now let's try it with short:
short c = -1; // "signed" is implied
unsigned short d = 2;
if (c < d)
puts("c < d"); // printed
else
puts("c >= d"); // not printed
First, the two operands are promoted. Since both can be represented faithfully by int, both are promoted to int.
Next, they are converted to the same type. But they already are the same type, int, so nothing is done.
So the comparison becomes if (-1 < 2), which is true.
Writing good code: There's an easy way to catch these "gotchas" in your code. Just always compile with warnings turned on, and fix the warnings. I tend to write code like this:
int x = ...;
unsigned y = ...;
if (x < 0 || (unsigned) x < y)
...;
You have to watch out that any code you do write doesn't run into the other signed vs. unsigned gotcha: signed overflow. For example, the following code:
int x = ..., y = ...;
if (x + 100 < y + 100)
...;
unsigned a = ..., b = ...;
if (a + 100 < b + 100)
...;
Some popular compilers will optimize (x + 100 < y + 100) to (x < y), but that is a story for another day. Just don't overflow your signed numbers.
Footnote: Note that while signed is implied for int, short, long, and long long, it is NOT implied for char. Instead, it depends on the platform.
Taken from the C++ standard:
4.5 Integral promotions [conv.prom] 1 An rvalue of type char, signed char, unsigned char, short int, or unsigned short int can be
converted to an rvalue of type int if int can represent all the values of the
source type; otherwise, the source rvalue can be converted to an
rvalue of type unsigned int.
In practice it means, that all operations (on the types in the list) are actually evaluated on the type int if it can cover the whole value set you are dealing with, otherwise it is carried out on unsigned int.
In the first case the values are compared as unsigned int because one of them was unsigned int and this is why -1 is "greater" than 2. In the second case the values a compared as signed integers, as int covers the whole domain of both short and unsigned short and so -1 is smaller than 2.
(Background story: Actually, all this complex definition about covering all the cases in this way is resulting that the compilers can actually ignore the actual type behind (!) :) and just care about the data size.)
The conversion process for C++ is described as the usual arithmetic conversions. However, I think the most relevant rule is at the sub-referenced section conv.prom: Integral promotions 4.6.1:
A prvalue of an integer type other than bool, char16_t, char32_t, or
wchar_t whose integer conversion rank ([conv.rank]) is less than the
rank of int can be converted to a prvalue of type int if int can
represent all the values of the source type; otherwise, the source
prvalue can be converted to a prvalue of type unsigned int.
The funny thing there is the use of the word "can", which I think suggests that this promotion is performed at the discretion of the compiler.
I also found this C-spec snippet that hints at the omission of promotion:
11 EXAMPLE 2 In executing the fragment
char c1, c2;
/* ... */
c1 = c1 + c2;
the ``integer promotions'' require that the abstract machine promote the value of each variable to int size
and then add the two ints and truncate the sum. Provided the addition of two chars can be done without
overflow, or with overflow wrapping silently to produce the correct result, the actual execution need only
produce the same result, possibly omitting the promotions.
There is also the definition of "rank" to be considered. The list of rules is pretty long, but as it applies to this question "rank" is straightforward:
The rank of any unsigned integer type shall equal the rank of the
corresponding signed integer type.

C usual arithmetic conversions

I was reading in the C99 standard about the usual arithmetic conversions.
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.
So let's say I have the following code:
#include <stdio.h>
int main()
{
unsigned int a = 10;
signed int b = -5;
printf("%d\n", a + b); /* 5 */
printf("%u\n", a + b); /* 5 */
return 0;
}
I thought the bolded paragraph applies (since unsigned int and signed int have the same rank. Why isn't b converted to unsigned ? Or perhaps it is converted to unsigned but there is something I don't understand ?
Thank you for your time :-)
Indeed b is converted to unsigned. However what you observed is that b converted to unsigned and then added to 10 gives as value 5.
On x86 32bit this is what happens
b, coverted to unsigned, becomes 4294967291 (i.e. 2**32 - 5)
adding 10 becomes 5 because of wrap-around at 2**32 (2**32 - 5 + 10 = 2**32 + 5 = 5)
0x0000000a plus 0xfffffffb will always be 0x00000005 regardless of whether you are dealing with signed or unsigned types, as long as only 32 bits are used.
Repeating the relevant portion of the code from the question:
unsigned int a = 10;
signed int b = -5;
printf("%d\n", a + b); /* 5 */
printf("%u\n", a + b); /* 5 */
In a + b, b is converted to unsigned int, (yielding UINT_MAX + 1 - 5 by the rule for unsigned-to-signed conversion). The result of adding 10 to this value is 5, by the rules of unsigned arithmetic, and its type is unsigned int. In most cases, the type of a C expression is independent of the context in which it appears. (Note that none of this depends on the representation; conversion and arithmetic are defined purely in terms of numeric values.)
For the second printf call, the result is straightforward: "%u" expects an argument of type unsigned int, and you've given it one. It prints "5\n".
The first printf is a little more complicated. "%d" expects an argument of type int, but you're giving it an argument of type unsigned int. In most cases, a type mismatch like this results in undefined behavior, but there's a special-case rule that corresponding signed and unsigned types are interchangeable as function arguments -- as long as the value is representable in both types (as it is here). So the first printf also prints "5\n".
Again, all this behavior is defined in terms of values, not representations (except for the requirement that a given value has the same representation in corresponding signed and unsigned types). You'd get the same result on a system where signed int and unsigned int are both 37 bits, signed int has 7 padding bits, unsigned int has 11 padding bits, and signed int uses a 1s'-complement or sign-and-magnitude representation. (No such system exists in real life, as far as I know.)
It is converted to unsigned, the unsigned arithmetic just happens to give the result you see.
The result of unsigned arithmetic is equivalent to doing signed arithmetic with two's complement and no out of range exception.

Resources