class: center, middle ### COMP26020 Programming Languages and Paradigms Part 1: C Programming *** # Type Conversion and Casting ??? - Hello everyone - In this video I will talk about type conversion and casting in C and C++ --- # Implicit Type Conversions ```c #include
int main(int argc, char **argv) { char char_var = 12; // 1 byte, -128 -to 127 int int_var = 1000; // 4 bytes, -2*10^9 to 2*10^9 long long ll_var = 0x840A1231AC154; // 8 bytes, -9*10^18 to 9*10^18 // here, char_var is first promoted to int, then the result of the first // addition is promoted to long long long long result = (char_var + int_var) + ll_var; printf("%lld\n", result); return 0; } ``` .codelink[
`15-preprocessor/implicit-type-conversion.c`
] ??? - In many situations the compiler performs **implicit conversions** between types - With arithmetic operations, it applies **integer promotion**, converting smaller types to larger ones - For example here we declare signed integers of various sizes - We have a char, note that we can put integers in chars, on x86-64 it's one byte so can store only 256 number inside - we also have a regular int, on x86-64 its size is 4 bytes so we can store about 4 billions numbers in there - and also a long long int on x86-64 its size is 8 bytes so it can store much more numbers - now if we look at the operation here we see that first the char being added to an int, the char is automatically promoted to an int, giving for what is between parentheses and int result - this value is then promoted to a long long because it's added with a long long, and the final result is a long long - we can store it in a long long variable without fear of loosing precision or data truncation due to the intermediary operations and types --- # Integer Promotion
1. Same type? no promotion 2. Same signed/unsigned attribute? lesser rank promoted to greater rank 3. Unsigned rank >= other operand rank? signed promoted to the unsigned rank 4. Signed type can represent all the values of the unsigned type? unsigned operand promoted to signed type 5. Otherwise, both operands converted to the unsigned type corresponding to the signed operand's type .small[ More info: https://en.cppreference.com/w/c/language/conversion ] ??? - The rationale behind integer promotion is that no information is lost when going from smaller to larger types - Integer types are given ranks as depicted on the slide - The promotion rules for 2 operands are as follows, in order - If the operands have the same type there is no need for promotion - If both operands are signed or both operands are unsigned, the operand of lesser rank is promoted to the type of the operand of higher rank - If the rank of the unsigned operand is superior or equal to the rank of the other operand, the signed operand is promoted to the type of the unsigned operand - If the signed operand type can represent all the values of the unsigned operand type, the unsigned operand gets promoted to the signed type 5. Otherwise, both operands are converted to the unsigned type corresponding to the signed operand's type --- # Integer Promotion ```c int si = -1; unsigned int ui = 1; printf("%d\n", si < ui); // prints 0! si converted to unsigned int ``` .codelink[
`15-preprocessor/integer-promotion.c`
] - Keep these rules in mind! ??? - You need to keep these rules in mind, without this may have nasty bugs when mixing signed and unsigned integers - Look at this example - We have a comparison between a signed int which is minus 1 and an unsigned int - According to the rules the signed int gets converted to an unsigned int - And the binary representation of minus 1 in memory when interpreted as an unsigned int, corresponds to the maximum value that can be stored in an unsigned int, which is about 4 billions - So the expression evaluates to false, which is 0 in C --- # Integer Promotion - Always remember the storage sizes of types ```c int main(int argc, char **argv) { int i = -1000; unsigned int ui = 4294967295; printf("%d\n", i + ui); // prints -1001 // i: 11111111111111111111110000011000 (A) // ui: 11111111111111111111111111111111 (B) // i + ui: 111111111111111111111110000010111 (C) // final: 11111111111111111111110000010111 (D) // (A) originally 2's complement promoted to unsigned // (B) standard unsigned representation, max number an unsigned int can store // (C) addition result // (D) result overflows 32 bits as an int (expected by %d), loosing MSB // Solution: use %ld rather than %d to store the result on 64 bits return 0; } ``` .codelink[
`15-preprocessor/integer-overflow.c`
] ??? - Another thing you need to keep in mind is the storage size of all types when you mix them in operations - Here's another example of behaviour that can be surprising - We add a signed int -1000 to an unsigned int that is actually the maximum value we can store in an unsigned int - so i is promoted to an unsigned int, because signed numbers are encoded with 2's complement you have the leading ones in the binary representation - ui being the max number that can be stored in an unsigned int, it is 32 bits full of ones - We do the addition, which overflows so the most significant bit gets truncated - And we obtain the binary representation of -1001 --- # Integer to Floating-Point Conversion ```c int main(int argc, char **argv) { //prints 7: 25/10 rounds to 2; 2 * 15 = 30; 30/4 rounds to 7 printf("%d\n", 25 / 10 * 15 / 4); // prints 7.5: 25/10 rounds to 2; 2*15 = 30; 30 gets converted to // 30.0 (double) and divided by 4.0 (double) giving result 7.5 (double) printf("%lf\n", 25 / 10 * 15 / 4.0); // prints 9.375: 25.0 / 10.0 (converted from 10) is 2.5, multiplied by 15.0 // (converted from 15) gives 37.5, divided by 4.0 (converted from 4) gives // 9.375 printf("%lf\n", 25.0 / 10 * 15 / 4); // prints garbage, don't try to interpret a double as an int! printf("%d\n", 25.0 / 10 * 15 / 4); return 0; } ``` .codelink[
`15-preprocessor/int-float-conversion.c`
] ??? - Regarding floating point numbers - If an operand is `float`/`double`, the other gets converted to `float`/`double` - This is another implicit conversion realised by the compiler - Conversion spreads from left to right - For example on the second line, 25 / 10 is an integer operation so we loose some information - The result is multiplied by 15 - And then divided by 4 which is a floating point number so there is no information loss on this particular operation --- # Parameter Passing ```c void f1(int i) { printf("%d\n", i); } void f2(double d) { printf("%lf\n", d); } void f3(unsigned int ui) { printf("%u\n", ui); } int main(int argc, char **argv) { char c = 'a'; unsigned long long ull = 0x400000000000; f1(c); // prints 97 (ascii code for 'a') f2(c); // prints 97.0 f3(ull); // overflows int ... prints 0 (lower 32 bits of 0x400000000000) return 0; } ``` .codelink[
`15-preprocessor/parameter-passing.c`
] ??? - Conversion also happens implicitly when calling functions - For example here we have a character variable containing A - passed as parameter to functions expecting int and double - Char are encoded with ascii code - So when we it is interpreted as an int or a double, we get the value of the code for A, 97 or 97.0 - We also have a long long unsigned variable that is passed to a function expecting an unsigned int but it's too large for that - So when printed we only see its lower 32 bits, 0 - Note that this code is perfectly legit and compilers will produce no warning - So you really need to be aware of what are the values that can be stored on which type, use sizeof if needed --- # Type Casting - **Casting** allows to force the type of an expression - syntax: `(type)expression` ```c // prints 3.75: 4 gets converted to 4.0 printf("%lf\n", (float)15/4); // prints 4: 2.5 converted to 2 (int), multiplied by 12 gives 24, divided // by 5 gives 4 printf("%d\n", ((int)2.5 * 12)/5); // prints 4.8: 2*12 = 24, converted to 24.0, divided by 5.0 gives 4.8 printf("%lf\n", ((int)2.5 * 12)/(double)5); return 0; ``` .codelink[
`15-preprocessor/type-casting-1.c`
] ```c int si = -1; unsigned int ui = 1; printf("%d\n", si < (int)ui); // prints 1 ``` .codelink[
`15-preprocessor/type-casting-2.c`
] ??? - Another thing I want to talk about is type casting - It allows you to force a conversion - The syntax is as follows: you put the type between parentheses in front of the expression - For example here I am forcing the conversion of an int into a float - On the second line I convert a floating point number into an integer - And on the last line I convert an integer into a double - Now remember this example with weird behaviour due to integer promotion? - We can fix that with a cast - We force the unsigned variable to be converted to a signed one, and we now compare two signed int - This will indeed print 1 --- # Generic Pointers .small[ - `void *` can be used as a generic pointer to pass data of different types ] .leftcol[ ```c typedef enum { CHAR, INT, DOUBLE } type_enum; void print(void *data, type_enum t) { switch(t) { case CHAR: printf("character: %c\n", *(char *)data); break; case INT: printf("integer: %d\n", *(int *)data); break; case DOUBLE: printf("double: %lf\n", *(double *)data); break; default: printf("Unknown type ...\n"); } } ``` ] .rigthcol[ ```c int main(int argc, char **argv) { char c = 'x'; int i = 42; double d = 11.5; print((void *)&c, CHAR); print((void *)&i, INT); print((void *)&d, DOUBLE); return 0; } ``` .codelink[
`15-preprocessor/generic-pointer.c`
] ] ??? - In combination with the special type void star, casting also allows us to implement generic pointers in C - That is pointers of the same type pointing to data of different type - Here is a function that takes a void * generic pointer as parameter - And according to a second parameter that is an enum, it will print the value of what is pointed by the pointer - It can be a char, an int, or a double - The trick is, when we call the function in question, we cast the pointers to these data types to void star - Of course we need to indicate the type somewhere, in this case we use the enum, --- # Summary - Type conversion - Implicit: integer promotion, integer to floating point types, parameter passing - Explicit: type casting ---- .center[Feedback form: https://bit.ly/2VDzVgG]
??? - Let's recap - We saw implicit type conversions, including integer promotion, integer to floating point conversions, and parameter passing - We also saw type casting, which are explicit conversions - In the next video we will talk about debugging