What is a fully promoted type? - c

I came across this in va_copy(3):
/* need a cast here since va_arg only
* takes fully promoted types */
c = (char) va_arg(ap, int);
What is a fully promoted type?

This is referring to the rules of integer promotion. Anytime an integer value with a type smaller than int (i.e. char, short) is used in a context where an int can be used, the value is promoted to an int.
In the case of a variadic function, the type of the arguments to a function are not known at compile time, so this promotion applies.
For example, suppose you had the following functions:
void f1(char c);
void f2(int count, ...);
They are called like this:
char x = 1;
f1(x); // x is passed as char
f2(1, x); // x is passed as int
This behavior is documented in section 6.3.1.1p2 of the C 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 .
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.

Related

What is the data type of the difference between two unsigned integers?

I've always taken this for granted before, but suppose I have:
uint8_t a;
uint8_t b;
if ((a - b) < 0) {
...
}
What is the data type of the expression (a - b)? Mr. Godbolt tells me that it's a signed value; is that guaranteed by the any of the C specifications?
AMENDMENT:
I now understand that type promotion will guarantee that (a-b) is an int when a and b are smaller than ints. What if instead a and b are unsigned ints?
unsigned int a;
unsigned int b;
if ((a - b) < 0) {
...
}
This expression will have type int, which is signed.
Because both operands have a type smaller than int, both will be promoted to type int, and the result will have type int.
Integer promotions are defined in section 6.3.1.1p2 of the C 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.
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.
So this means the expression ((a - b) < 0) could potentially evaluate as true.
Had the variables been defined like this:
unsigned int a;
unsigned int b;
Then there would be no promotion and a - b would have unsigned type, meaning ((a - b) < 0) would always be false.

C: int or unsigned int which type I used for pointer increasement

For this situation:
int arr[] = {0, 1, 2};
void func (int* arr_in){
int offset_0 = 0;
int offset_1 = 1;
int offset_2 = 2;
printf("%d\n", *(arr_in + offset_0) );
printf("%d\n", *(arr_in + offset_1) );
printf("%d\n", *(arr_in + offset_2) );
}
The compiler will not complain whether the I use is int or unsigned.
Two of results also seems correctly.
$ clang test.c -Wall -o test
I refer to the chapter §6.5.6/8 in the draft C11:
When an expression that has integer type is added to or subtracted from a pointer, the
result has the type of the pointer operand.
In the draft, there is no mention of "integer" which is (signed)int or unsigned.
So both there can be used for pointer operand on all platform?
In this context "an expression that has integer type" refers to any integer type, e.g. signed or unsigned char, short, int, long, or long long, as well as any other integer types defined by the implementation.
So you can safely use arguments of type int or unsigned int with a pointer, provided the resulting pointer still points to the same object or array.
Just read the C Standard carefully.
From the C Standard (6.2.5 Types)
17 The type char, the signed and unsigned integer types, and the
enumerated types are collectively called integer types. The integer
and real floating types are collectively called real types.
and now you can reread your quote from the C Standard
When an expression that has integer type is added to or subtracted
from a pointer, the result has the type of the pointer operand.
So you may use even the type char that is neither signed or unsigned integer type.
int = 1 and unsigned int = 1 are same thing, int = -1 and ungisned int = -1 and not the same thing.. If you use 1, 2 and 3, the answer is yes you can "call" it int, unsigned int or whatever you want. If you want the offset to be negative for example you can't use unsigned int, or if you want the offset to be larger then 2^31, then you can't use unsigned int.

Why type casting not required?

Below are following code written in c using CodeBlocks:
#include <stdio.h>
void main() {
char c;
int i = 65;
c = i;
printf("%d\n", sizeof(c));
printf("%d\n", sizeof(i));
printf("%c", c);
}
Why when printing variable c after it was assigned with int value (c = i), there no need for casting to be made?
A cast is a way to explicitly force a conversion. You only need casts when no implicit conversions take place, or when you wish the result to have another type than what implicit conversion would yield.
In this case, the C standard requires an implicit conversion through the rule for the assignment operator (C11 6.5.16.1/2):
In simple assignment (=), the value of the right operand is converted to the type of the
assignment expression and replaces the value stored in the object designated by the left
operand.
char and int are both integer types. Which in turn means that in this case, the rules for converting integers are implicitly invoked:
6.3.1.3 Signed and unsigned integers
When a value with integer type is converted to another integer type other than _Bool, if
the value can be represented by the new type, it is unchanged.
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.
Otherwise, the new type is signed and the value cannot be represented in it; either the
result is implementation-defined or an implementation-defined signal is raised.
In your case, the new type char can be either signed or unsigned depending on compiler. The value 65 can be represented by char regardless of signedness, so the first paragraph above applies and the value remains unchanged. For other larger values, you might have ended up with the "value cannot be represented" case instead.
This is a valid conversion between integer types, so no cast is necessary.
Please note that strictly speaking, the result of sizeof(c) etc is type size_t and to print that one correctly with printf, you must use the %zu specifier.
This assignment is performed on compatible types, because char is not much more than a single byte integer, whereas int is usually 4bytes integer type (machine dependant). Still - this (implicit) conversion does not require casting, but You may loose some information in it (higher bytes would get truncated).
Let's examine your program:
char c; int i = 65; c = i; There is no need for a cast in this assignment because the integer type int of variable i is implicitly converted to the integer type of the destination. The value 65 can be represented by type char, so this assignment is fully defined.
printf("%d\n", sizeof(c)); the conversion specifier %d expects an int value. sizeof(c) has value 1 by definition, but with type size_t, which may be larger than int. You must use a cast here: printf("%d\n", (int)sizeof(c)); or possibly use a C99 specific conversion specifier: printf("%zu\n", sizeof(c));
printf("%d\n", sizeof(i)); Same remark as above. The output will be implementation defined but most current systems have 32-bit int and 8-bit bytes, so a size of 4.
printf("%c", c); Passing a char as a variable argument to printf first causes the char value to be promoted to int (or unsigned int) and passed as such. The promotion to unsigned int happens on extremely rare platforms where char is unsigned and has the same size as int. On other platforms, the cast is not needed as %c expects an int argument.
Note also that main must be defined with a return type int.
Here is a modified version:
#include <stdio.h>
#include <limits.h>
int main() {
char c;
int i = 65;
c = i;
printf("%d\n", (int)sizeof(c));
printf("%d\n", (int)sizeof(i));
#if CHAR_MAX == UINT_MAX
/* cast only needed on pathological platforms */
printf("%c\n", (int)c);
#else
printf("%c\n", c);
#endif
return 0;
}

Integer promotions in C will downgrade `long int`?

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 :)

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