Integer promotions in C will downgrade `long int`? - c

From the C standard
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.
So this means that if I use a long int in an expression it will be downgraded to an unsigned int?

The bit you quoted is restricted by the text above it:
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 whose integer conversion rank is less than the rank of int and unsigned int.
A bit-field of type _Bool, int, signed int,or unsigned int.
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. All other types are unchanged
by the integer promotions.
In other words, long int doesn't get promoted to int or unsigned int.

I think "original type" refers to "[...] 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", as defined earlier in section 6.3.1.1.2. But, nice try :)

Related

How does type conversion and integer promotion work for stdint.h?

In C, I understand type conversions, integer promotion, casting, etc. for standard types, but how do the stdint.h types factor into this?
For type rankings, the rules state:
No two signed integer types shall have the same rank, even if they have the same representation.
The rank of any unsigned integer type shall equal the rank of the corresponding signed integer type, if any.
So assuming an int is 32 bits, does this mean int > int32_t = uint32_t > short int in the rankings?
Also, are the stdint.h types also subject to integer promotion? For example if I try to add a signed char to a uint32_t, they will both get promoted to unsigned ints?
To answer your first question: no. Since int32_t is usually defined with a typedef like this
typedef int int32_t;
it is the same as int and will have the same rank as int.
To answer the second question: yes. Integer promotion still applies. The types defined in stdint.h behave just like the types they are aliases of.
By the way, to be more confident in how your compiler behaves, you can test all of these things in your compiler by writing invalid code like this and carefully looking at the error message, which will (if you have a good compiler) reveal the type of the expression on the right hand side:
void * x = (signed char)-1 + (uint32_t)0;
According to the C Standard
— The rank of any standard integer type shall be greater than the rank
of any extended integer type with the same width.
The exact integer types for 2's complement representation are defined as tyoedef aliases of standard integer types.
From the C Standard (7.20.1.1 Exact-width integer types)
...and (for the signed types) that have a two’s complement
representation, it shall define the corresponding typedef names.
So this relational when the type int has 32 bits (for 2's complement representation)
int > int32_t = uint32_t > short int
is correct except that the relation int > int32_t provided that the type int32_t is an alias name for the type int introduced by a typedef declaration..
Also, are the stdint.h types also subject to integer promotion? For
example if I try to add a signed char to a uint32_t, they will both
get promoted to unsigned ints?
Here the object of the type unsigned char is promoted to the type int and the object of the type uint32_t is promoted to the type unsigned int (provided that int has 32-bits) due to the integer promotions
From the C Standard
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. 58) All other types are unchanged by the integer
promotions.
And then the object of the type int is converted to the type unsigned int due to the usual arithmetic conversions.
From the C Standard (6.3.1.8 Usual arithmetic conversions)
Otherwise, both operands are converted to the unsigned integer type
corresponding to the type of the operand with signed integer type.
Pay attention to then the name uint32_t can be an alias for the type unsigned int introduced by a typedef declaration. In this case uint32_t is the same type as unsigned int.

About integer promotion

According to C17 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.
Does 'all values' mean whole identifiers in same scope?
Then when some identifiers are with variable which can't be represented by int (such as long long int) in same identifier scope, there are no any promotion for identifiers?
Does 'all values' mean whole identifiers in same scope?
I'm not sure why you think all identifiers in a scope matter. The integer promotion applies to a single item, be it an object (e.g., variable) or expression.
What it's saying is that, if every possible value of this item (already one of the types guaranteed to fit into an int or unsigned int as per the paragraphs before your quote(a)) can be represented by the int type, it gets promoted to an int. Otherwise it gets promoted to an unsigned int.
(a) The text states, in full:
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.
A bit-field of type _Bool, int, signed int, or unsigned int.
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. All other types are unchanged by the integer promotions.

Implicit integer type conversion in C

I understand the implicit conversions of the C language between integer and floating point types, but I have a question for signed/unsigned implicit type conversions.
If you add, for example, an unsigned char and a signed int, what will be the resulting type? Would it be an unsigned int, a signed int, or something else?
I don't see anything specific in the C99 ANSI standard about this, so any help is appreciated.
In C99, the reference is 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. 51)
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.
Addition performs the usual arithmetic conversions, so, when adding unsigned char and signed int, either:
first the unsigned char is promoted to int, and then both types are the same, so the result has type int, or
(uncommon) int cannot represent all possible unsigned char values. In this case, unsigned char is promoted to unsigned int, and the third sub-bullet applies: unsigned int has equal rank to int, so the int operand is converted to unsigned int, and the result has type unsigned int.
It will almost certainly be a signed int and it depends on the system where the code runs. Check the paragraph Integral promotion here
unsigned char or unsigned short can be converted to int if it can hold
its entire value range, and unsigned int otherwise.
On a POSIX system for example it will definitely be a signed int as a char is always 8bits and an int is at least 16bits. Therefore an int can represent every possible value of an unsigned char. There are apparently systems where char is more that 8bits.

operations with different int types

I have a program which uses multiple different int types.
Most often used are uint64_t and the standard int. However I wonder if I can safely do operations mixed between them.
For instance I have an uint64_t and I want to add an int to it and store that value as another uint64_t.
Is doing such a thing safe? Do I have to cast the int to uint64_t before I can use operations on it?
I can`t really find stuff about it online. It might just be allowed and no one questions it or my Google queries are wrong.
Anyway so basically my question is can I mix and do operations with different types of ints?
Yes you can.
Your compiler will take care of the conversions. The only thing to worry about is overflow - if you store the result in a container that is smaller than the inputs.
The Google search term you need is "implicit type conversions" - see for example http://pic.dhe.ibm.com/infocenter/ratdevz/v8r5/index.jsp?topic=%2Fcom.ibm.tpf.toolkit.compilers.doc%2Fref%2Flangref_os390%2Fcbclr21011.htm
That link includes the following table:
Arithmetic conversion proceeds in the following order:
Operand Type Conversion
---------------------------------------------+--------------------------------------------
One operand has long double type | The other operand is converted to long double type.
---------------------------------------------+--------------------------------------------
One operand has double type | The other operand is converted to double.
---------------------------------------------+--------------------------------------------
One operand has float type | The other operand is converted to float.
---------------------------------------------+--------------------------------------------
One operand has unsigned long long int type | The other operand is converted to unsigned long long int.
---------------------------------------------+--------------------------------------------
One operand has long long int type | The other operand is converted to long long int.
---------------------------------------------+--------------------------------------------
One operand has unsigned long int type | The other operand is converted to unsigned long int.
---------------------------------------------+--------------------------------------------
One operand has unsigned int type |
and the other operand has long int type |
and the value of the unsigned int can be |
represented in a long int | The operand with unsigned int type is converted to long int.
---------------------------------------------+--------------------------------------------
One operand has unsigned int type |
and the other operand has long int type |
and the value of the unsigned int cannot be |
represented in a long int | Both operands are converted to unsigned long int
---------------------------------------------+--------------------------------------------
One operand has long int type | The other operand is converted to long int.
---------------------------------------------+--------------------------------------------
One operand has unsigned int type | The other operand is converted to unsigned int.
---------------------------------------------+--------------------------------------------
Both operands have int type | The result is type int.
---------------------------------------------+--------------------------------------------
C standard says,
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.
Therefore as int & unsigned int are of same rank you can add them, and when you add them int is converted to unsigned int leaving the result again in to unsigned int.

Integer promotion for `long` and `size_t` when sent through ellipsis?

View this question from the perspective of someone implementing printf.
Since the arguments of printf are passed through an ellipsis (...), they get integer promoted. I know that char, short and int get promoted to int while long long does not get promoted. Similarly for their unsigned counterparts.
This implies that when reading the varargs, va_arg(args, int) should be used for char, short and int while va_arg(args, long long) should be used for long long.
My question is, do long and size_t get promoted, and if they do, to what? There are many sources on the internet on integer promotion, but I haven't seen any that talk about these types.
P.S. I would appreciate a reference to the standard.
The integer conversion rank of long is required to be greater than the rank of int (6.3.1.1p1), so va_arg(args, long) is required even if long has the same representation (and precision) as int. Note that on most 64-bit platforms, long is 64-bit; Windows (an LLP64 platform) is an exception.
size_t is required to be an unsigned integer type (6.5.3.4p5, 7.19p2) and is recommended to have an integer conversion rank no greater than that of long int (7.19p4); it is required to have a precision of at least 16 bits (7.20.3p2, minimum value of SIZE_MAX). It is not required to be a (typedef to a) standard integer type, although it is allowed to be.
There are then three possibilities for the integer conversion rank of size_t:
It is less than that of int, so a size_t argument will be promoted to either int (if the precision of size_t is less than that of int) or unsigned int (if the two types have the same precision). In either case you would need to write va_arg(args, unsigned int) (even if the size_t argument is promoted to int, using the equivalent unsigned type is allowed by 7.16.1.1p2).
It is the same as that of int, i.e. size_t is the same type as unsigned int. In this case either va_arg(args, unsigned int) or va_arg(args, size_t) are allowed.
It is greater than that of int. In this case va_arg(args, size_t) must be used.
Note that either of 1 and 3 can obtain even if the precision of size_t is the same as that of int.
This means that to extract a size_t parameter using va_arg, it is necessary to know or infer the integer conversion rank of size_t. This can be done using a type-generic macro (6.5.1.1):
#define va_arg_size_t(args) _Generic((+(sizeof(0))), \
int: (size_t) va_arg((args), unsigned int), \
unsigned int: (size_t) va_arg((args), unsigned int), \
default: va_arg((args), size_t))
If size_t is promoted to int by the unary plus operator as used above, then we extract an unsigned int; if size_t is promoted to unsigned int, or is a typedef to unsigned int, then we extract an unsigned int; if it is not promoted and is a distinct type from unsigned int, then we hit the default block. We can't supply size_t itself as an option, as that would conflict if size_t were a typedef for unsigned int.
Note that this is a problem not restricted to size_t, ptrdiff_t and wchar_t have the same issue (for the latter, wint_t can hold any wchar_t value and is not subject to promotion, but there is no guarantee that wchar_t is promoted to wint_t, unlike the guarantee that char is promoted to int). I'd suggest that the standard needs to introduce new types spromo_t, ppromo_t and wpromo_t, and similarly for the types in stdint.h. (Sure, you can use _Generic as above, but it's a pain in the neck.)
C says (emphasis mine):
(C99, 6.3.1.1p2) " 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 whose integer conversion rank is less
than or equal to the rank of int and unsigned int.
— A bit-field of type _Bool, int, signed int, or unsigned int.
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."
Arguments of type long won't be promoted. The integer promotions relevant to size_t can be summarized as follows:
if size_t is in range of int, promote to int
if size_t is in range of unsigned int, promote to unsigned int
otherwise, size_t has greater conversion rank (and thus width) than unsigned int and no promotion happens
The trivial case is if size_t is an alias for or has greater width than unsigned int. In these cases, no promotions happen and you can use size_t to read the variadic argument.
The edge cases are
size_t is in range of int
size_t is not in range of int, but in range of unsigned int without actually being unsigned int
The latter can happen if int contains padding of if size_t is an extended integer type of same width as unsigned int.
Assuming integer representations do not contain padding bits, you can cover both edge cases by reading an unsigned int variadic argument, which will work on all reasonable implementations of the C language despite possibly being undefined behaviour.

Resources