Expression evaluation in C involves the interplay of operator precedence, associativity, and type conversions. The order of evaluation for subexpressions is influenced by these factors, with implicit type conversions playing a crucial role in ensuring correct computations.
Implicit Type Conversions
Implicit type conversions occur automatically in expressions and are categorized into integer promotion and arithmetic conversion.
Integer Promotion
Integer promotion ensures that arithmetic operations on integer types smaller than int are performed with atleast int precision. This conversion applies to char and short types, which are promoted to int or unsigned int before computation.
The rationale behind integer promotion lies in CPU architecture. Most CPUs process integer operations using registers and arithmetic logic units (ALUs) designed for int-sized data. Direct operations on smaller types like 8-bit char are inefficient or unsupported, necessitating promotion to standard int size.
Promotion Rules:
- For signed types, promotion extends the sign bit to fill the higher bits.
- For unsigned types, higher bits are filled with zeros.
Example 1: Character Arithmetic
#include <stdio.h>
int main() {
unsigned char x = 200;
unsigned char y = 100;
unsigned char z = x + y;
int result = x + y;
printf("z = %d\n", z);
printf("result = %d\n", result);
return 0;
}
In this example, x and y undergo integer promotion before addition. The result stored in z may overflow the unsigned char range, while result retains the full promoted value.
Example 2: Type Comparison
#include <stdio.h>
int main() {
short m = 0x8000;
int n = 0x80000000;
if (m == 0x8000) printf("m matched\n");
if (n == 0x80000000) printf("n matched\n");
return 0;
}
Here, m is promoted to int before comparison, potentially altering its value due to sign extension, whereas n requires no promotion.
Example 3: Expression Context
#include <stdio.h>
int main() {
char val = 10;
printf("%zu\n", sizeof(val));
printf("%zu\n", sizeof(+val));
printf("%zu\n", sizeof(-val));
return 0;
}
The unary + and - operators trigger integer promotion, causing sizeof(+val) and sizeof(-val) to yield the size of int rather than char.
Arithmetic Conversion
When operands in an expression have different types, arithmetic conversion aligns them to a common type according to a hierarchy:
long doubledoublefloatunsigned long intlong intunsigned intint
The lower-ranked type is converted to the higher-ranked type before operation.
Operator Properties
Expression evaluation is governed by three operator attributes:
- Precedence: Determines which adjacent operator is evaluated first.
- Associativity: Applies when adjacent operators have equal precedence, dictating evaluation direction (left-to-right or right-to-left).
- Sequence Points: Certain operators (
&&,||,?:,,) enforce specific evaluation orders.
Example 4: Precedence and Associativity
#include <stdio.h>
int main() {
int p = 2 * 3 + 4 / 2;
int q = 10 - 5 - 1;
return 0;
}
Multiplication and division have higher precedence than addition and subtraction, and subtraction associates left-to-right.
Example 5: Undefined Evaluation Order
#include <stdio.h>
int main() {
int a = 5;
int b = a++ + ++a;
printf("%d\n", b);
return 0;
}
The order of evaluating the increments relative to the addition is unspecified, leading to unpredictable results.
Example 6: Function Call Order
#include <stdio.h>
int counter() {
static int cnt = 0;
return ++cnt;
}
int main() {
int res = counter() - counter() * counter();
printf("%d\n", res);
return 0;
}
The sequence of function calls is not determined by operator precedence, potentially yielding different outcomes.
Example 7: Complex Expressions
#include <stdio.h>
int main() {
int idx = 1;
int total = (++idx) + (++idx) + (++idx);
printf("%d\n", total);
return 0;
}
The interleaving of side effects from multiple increments creates ambiguity, making the expression's behavior undefined.
To ensure reliable code, avoid overly complex expressions. Break them into clear, sequential steps to eliminate ambiguity in evaluation order.