Why type casting not required? - c

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;
}

Related

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.

Promotions and conversions of variables in printf()

In this case,
#include <stdio.h>
int main()
{
unsigned char a = 1;
printf("%hhu", -a);
return 0;
}
The argument -a in printf is promoted to int by the integer promotion by the unary minus operator and subsequently promoted by the default argument promotion and finally converted to unsigned char by the format specifier.
So -a => -(int)a(by ~) => no conversion by function call => (unsigned char)-(int)a(by %hhu). Is my thought right?
You are correct that a is promoted to int in -a, and that printf("%hhu", -a); passes an int to printf. The notional conversion performed with %hhu is not clear.
Note that if a is not zero, then -a produces a value (in an int) that is not an unsigned char value. Further, with two’s complement eight-bit signed char, if a is greater than 128, then -a produces a value that is not a signed char value.
To understand %hhu, we look at the specification for u in C 2018 7.21.6.1 8:
The unsigned int argument is converted to unsigned octal (o), unsigned decimal (u),…
and for hh in 7.21.6.1 7:
Specifies that a following d, i, o, u, x, or X conversion specifier applies to a signed char or unsigned char argument (the argument will have been promoted according to the integer promotions, but its value shall be converted to signed char or unsigned char before printing);…
First we have to resolve this issue of “signed char or unsigned char”. Does this say we can pass either a signed char or an unsigned char for %hhu? I think not; I think the authors have just put together the language for %hhd (intended to convert a signed char) and %hhu (intended to convert an unsigned char). So I believe the intent is that a promoted unsigned char should be passed for the %hhu conversion specification.
Apple Clang 11.0.0 seems to agree, when passing -a (but not a), it warns: “warning: format specifies type 'unsigned char' but the argument has type 'int' [-Wformat]”
As noted above, passing -a may pass a value that cannot result from passing a promoted unsigned char. It may even pass a value that cannot result from passing a promoted signed char or unsigned char. In this case, it can be argued we have violated the requirement to pass an unsigned char, and therefore the C standard does not specify the resulting behavior. Even though it says the passed value shall be converted to an unsigned char, I believe that is a notional conversion, not a specific requirement on the library implementation, and that is also falls under the “as if” rules: It does not actually have to be performed if the resulting defined behavior of programs is the same. But, since passing an improper value may not be defined, we do not have defined behavior.
That may be a strict reading of the rules, but it would not surprise me greatly if printf printed “4294967295” instead of “255” when a were 1.
printf is a variadic function. The type of the arguments passed by ... parameter are not known inside the function. As such, any variadic function must rely on other mechanisms to interpret the type of the va_args arguments. printf and family use a const char* format string to "tell them" what kind of arguments were passed. Passing a type different then the expected type as specified by it's format specifier results in Undefined Behavior.
For instance:
printf("%f", 24)
Is undefined behavior. There is no conversion from int to float anywhere because the arguments are passed as they are (after promotion) and inside the printf the function incorrectly treats its first argument as float. printf does not know and can't know that the real type of the argument is int.
Variadic arguments undergo some promotions of their own. Of interest for your question unsigned char is promoted to int or unsigned int (I am not sure tbo). As such there is no way for a variadic parameter to actually be of type unsigned char. So hhu while is indeed the specifier for unsigned char it will actually expect an unsigned int (int), which is what you pass to it.
So afaik the code is safe because of the two integer promotions caused by unary minus and passing variadic arguments. I am not 100% sure though. Integer promotions are weird and complicated.

Can integer promotion happen in the reverse order(eg. long int to int) in variadic functions like printf()?

#include<stdio.h>
int main() {
long a = 9;
printf("a = %d",a);//output is 9 but with a warning 'expecting long int'
}
Why can't long here be converted to int?
Variadic functions in general and printf family in particular, are odd special cases. They are notorious for their non-existent type safety, so if you pass the wrong type or use the wrong format string, you invoke undefined behavior and anything can happen.
In your case, most likely int and long happen to have the same representation so the program works despite the warning.
In the case of a regular function though, there is a kind of "demotion" taking place if you pass a larger integer type to a function expecting a smaller one. When this happens, you trigger a conversion from the larger type to the smaller, which is well-defined. (The result will however be compiler-specific if you mix types of different signedness.)
Compilers tend to warn against such implicit conversions, so it is better to do the conversion explicitly with a cast.
Because that's the way variadic functions behave in C language. printf is just a function from the standard library and has no special processing. It is declared as
int printf(const char restrict *fmt, ...);
And the standard (n1256 draft for C99) says (emphasize mine):
6.5.2.2 Function calls...
6 If the expression that denotes the called function has a type that does not include a
prototype, the integer promotions are performed on each argument, and arguments that
have type float are promoted to double. These are called the default argument
promotions...
7 ... The ellipsis notation in a function prototype declarator causes
argument type conversion to stop after the last declared parameter. The default argument
promotions are performed on trailing arguments.
That means that on all parameters to printf, float are converted to double and integer promotions occur on integral arguments.
And in 6.3.1.1 Arithmetic operands / Boolean, characters, and integers
2 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.
So as long has a rank greater than int, it is left unchanged by an integer promotion, and the format shall be adapted to accept a long:
long a = 9;
printf("a = %ld",a);
Following passes a long, yet printf() expects an int due to "%d". Result: undefined behavior (UB). If int and long are the same size that UB might look like everything is OK, or it may fail. It is UB.
long a = 9;
printf("a = %d",a); // UB
Why can't long here be converted to int?
long can be converted, yet code did not direct that like the below code.
printf("a = %d", (int) a); // OK
Can integer promotion happen in the reverse order ... in variadic functions like printf()?
Integer promotions do not happen in the reverse order unless code explicitly down-casts or assigned to a narrower type.
There are cases where demotion will appear to be true with printf().
The below promotes sc to int as it is passed to printf(). printf() will take that int and due to "%hhd" will convert it to signed char and then print that numeric value.
signed char sc = 1;
printf("a = %hhd", cs); // prints 1
The below passes i to printf() as an int. printf() will take that int and due to "%hhd" will convert it to signed char and then print that numeric value. So in this case it looks like i was demoted.
int i = 0x101;
printf("a = %hhd", i); // prints 1

Why gcc does not produce type mismatch warning for int and char?

Why compiling the following code in gcc does not produce any type mismatch warning? -1 is of type int, and f() expects type char:
void f(char c) {}
int main(void)
{
f(-1);
return 0;
}
Even if we explicitly specify the types, there is no warning:
void f(unsigned char c) {}
int main(void)
{
f((signed int)-1);
return 0;
}
What is curious: if we specify out-of-range value, the warning is printed:
void f(char c) {}
int main(void)
{
f(65535);
return 0;
}
warning: overflow in implicit constant conversion
gcc version 6.1.1
An int can be converted to a char. In int is allowed to be converted to a char in both C and C++.
From the C11 Standard:
6.3.1.3 Signed and unsigned integers
1 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.
2 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.
3 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.
From the C++11 Standard:
4.7 Integral conversions
1 A prvalue of an integer type can be converted to a prvalue of another integer type. A prvalue of an unscoped enumeration type can be converted to a prvalue of an integer type.
2 If the destination type is unsigned, the resulting value is the least unsigned integer congruent to the source
integer ...
3 If the destination type is signed, the value is unchanged if it can be represented in the destination type (and bit-field width); otherwise, the value is implementation-defined.
If char is a signed type, it can easily hold the value -1. Hence, the behavior is predictable. The integral value of c in f will be -1. When unsigned char is used, the value of c will be an implementation-defined value but it is still allowed under both the standards.
Seems like a flaw in gcc's Wconversion warning option.
If enabled, this warning option warns for assignments:
int i = c; //where c is of type char
and passing variables to functions:
f(i); //where i is of type int
but does not warn for passing integer literals:
f(-1); //where -1 is of type int
According to the C standard the last example should also produce a warning.
Gcc is smart enough to recognize the integer literal fits into a char type, and doesn't warn, whereas if a value that doesn't fit is used, it does warn.
This is reasonable behavior, although a pedantic user would except a warning in the last example that should be silenced by a cast to type char.
Gcc actually includes a warning option to warn when a sign of an integer type is changed through implicit conversion. Use: -Wsign-conversion, and you will get a warning for the second example.

unusual output of the hexadecimal number

#include<stdio.h>
int main()
{
int a = 0xabcdef;
char *b = &a;
printf("%x",*b);
return 0;
}
above code gives output as ffffffef. What is the reason of this output?
Firstly, variable b is declared with type char *. You are trying to initialize it with value &a of type int *. This is illegal. The code is not valid C. The behavior is undefined.
Secondly, if your compiler managed to let this initialization slip through, then it probably performed an implicit conversion of the int * value to char * type. This makes pointer b to point to just one byte of variable a. The value of that single byte is what you see through *b. Which byte that is (in terms of the original value of a) is implementation-defined. E.g. you will typically you get a lower-order byte (0xEF) or a higher-order byte (0xAB).
Thirdly, it is implementation-defined whether type char is signed or unsigned. That means that the value of *b might be signed or unsigned, depending on your implementation. When you pass a char value *b to a variadic function printf, that char value is automatically converted to int type. The result of that conversion will generally depend on the signedness of type char. If your b points to the 0xEF byte, char is signed and int is 32-bit wide and uses 2's-complement, then the result, in hex terms, will look as 0xFFFFFFEF (which is a negative value).
Fourthly, %x format specifier requires an unsigned int argument, but instead you are passing an int argument with potentially negative value. The behavior is undefined.
In other words, your program is just a big pile of undefined and implementation-defined behavior. There's not much point in trying to explain why you got that specific output.
The pointer *b is a char type pointer and it's pointing to a int type variable. That's the reason of wrong output. Declare the pointer variable *b as int type.
int a = 0xabcdef;
int *b = &a;
printf("%x",*b);

Resources