Chapter 4, “Variables, Data Types, and Arithmetic Expressions,” briefly addressed the fact that sometimes conversions are implicitly made by the system when expressions are eval- uated. The case you examined was with the data types float and int.You saw how an operation that involved a float and an int was carried out as a floating-point opera- tion, the integer data item being automatically converted to floating point.
You have also seen how the type cast operator can be used to explicitly dictate a con- version. So in the statement
average = (float) total / n;
the value of the variable total is converted to type float before the operation is per- formed, thereby guaranteeing that the division will be carried out as a floating-point operation.
The C compiler adheres to strict rules when it comes to evaluating expressions that consist of different data types.
The following summarizes the order in which conversions take place in the evalua- tion of two operands in an expression:
- If either operand is of type long double, the other is converted to long double, and that is the type of the result.
- If either operand is of type double, the other is converted to double, and that is the type of the result.
- If either operand is of type float, the other is converted to float, and that is the type of the result.
- If either operand is of type _Bool, char, short int, bit field, or of an enumer- ated data type, it is converted to int.
- If either operand is of type long long int, the other is converted to long long int, and that is the type of the result.
- If either operand is of type long int, the other is converted to long int, and that is the type of the result.
- If this step is reached, both operands are of type int, and that is the type of the result.
This is actually a simplified version of the steps that are involved in converting operands in an expression. The rules get more complicated when unsigned operands are involved. For the complete set of rules, refer to Appendix A, “C Language Summary.”
Realize from this series of steps that whenever you reach a step that says “that is the type of the result,” you’re done with the conversion process.
As an example of how to follow these steps, see how the following expression would be evaluated, where f is defined to be a float, i an int, l a long int, and s a short int variable:
f * i + l / s
Consider first the multiplication of f by i, which is the multiplication of a float by an int. From step 3, you find that, because f is of type float, the other operand, i, is also converted to type float, and that is the type of the result of the multiplication.
Next, the division of l by s occurs, which is the division of a long int by a short int. Step 4 tells you that the short int is promoted to an int. Continuing, you find from step 6 that because one of the operands (l) is a long int, the other operand is converted to a long int, which is also the type of the result. This division, therefore, produces a value of type long int, with any fractional part resulting from the division truncated.
Finally, step 3 indicates that if one of the operands in an expression is of type float (as is the result of multiplying f * i), the other operand is converted to type float, which is the type of the result. Therefore, after the division of l by s has been per- formed, the result of the operation is converted to type float and then added into the product of f and i. The final result of the preceding expression is, therefore, a value of type float.
Remember, the type cast operator can always be used to explicitly force conversions and thereby control the way that a particular expression is evaluated.
So, if you didn’t want the result of dividing l by s to be truncated in the preceding expression evaluation, you could have type cast one of the operands to type float, thereby forcing the evaluation to be performed as a floating-point division:
f * i + (float) l / s
In this expression, l would be converted to float before the division operation was performed, because the type cast operator has higher precedence than the division operator. Because one of the operands of the division would then be of type float, the other (s) would be automatically converted to type float, and that would be the type of the result.
1. Sign Extension
Whenever a signed int or signed short int is converted into an integer of a larger size, the sign is extended to the left when the conversion is performed. This ensures that a short int having a value of –5, for example, will also have the value –5 when con-verted to a long int. Whenever an unsigned integer is converted to an integer of a larg-er size, as you would expect, no sign extension occurs.
On some systems (such as Mac G4/G5 and Pentium processors) characters are treated as signed quantities. This means that when a character is converted to an integer, sign extension occurs. As long as characters are used from the standard ASCII character set, this fact will never pose a problem. However, if a character value is used that is not part of the standard character set, its sign might be extended when converted to an integer. For example on a Mac, the character constant ‘\377’ is converted to the value –1 because its value is negative when treated as a signed, eight-bit quantity.
Recall that the C language permits character variables to be declared unsigned, thus avoiding this potential problem. That is, an unsigned char variable will never have its sign extended when converted to an integer; its value will always be greater than or equal to 0. For the typical eight-bit character, a signed character variable, therefore, has the range of values from -128 to +127, inclusive. An unsigned character variable can range in value from 0 to 255, inclusive.
If you want to force sign extension on your character variables, you can declare such
variables to be of type signed char. This ensures that sign extension will occur when the character value is converted to an integer, even on machines that don’t do so by default.
2. Argument Conversion
You have used prototype declarations for all the functions that you have written in this book. In Chapter 8, “Working with Functions,” you learned this was prudent because you can physically locate the function either before or after its call, or even in another source file, with a prototype declaration. It was also noted that the compiler automatical- ly converts your arguments to the appropriate types as long as it knows the types of arguments the function expects. The only way it can know this is by having previously encountered the actual function definition or a prototype declaration.
Recall that, if the compiler sees neither the function definition nor a prototype decla- ration before it encounters a call to a function, it assumes the function returns an int. The compiler also makes assumptions about its argument types. In the absence of infor- mation about the argument types to a function, the compiler automatically converts _Bool, char, or short arguments to ints and converts float arguments to double.
For example, assume that the compiler encounters in your program
y = absoluteValue (x);
Having not previously seen the definition of the absoluteValue function, and with no prototype declaration for it either, the compiler generates code to convert the value stored inside the float variable x to double and passes the result to the function. The compiler also assumes the function returns an int.
If the absoluteValue function is defined inside another source file like this:
float absoluteValue (float x)
if ( x < 0.0 )
x = -x;
you’re in trouble. First, the function returns a float, yet the compiler thinks it returns an int. Second, the function expects to see a float argument, but you know the compiler will pass a double.
Remember, the bottom line here is that you should always include prototype declara- tions for the functions you use. This prevents the compiler from making mistaken assumptions about return types and argument types.
Now that you have learned more about data types, it’s time to learn about how to work with programs that can be split into multiple source files. Chapter 15 covers this topic in detail. Before you start that chapter, try the following exercises to make certain you understand the concepts you just learned.
Source: Kochan Stephen G. (2004), Programming in C: A Complete Introduction to the C Programming Language, Sams; Subsequent edition.