Previous Page
Next Page

5.1. How Expressions Are Evaluated

Before we consider specific operators in detail, this section explains a few fundamental principles that will help you understand how C expressions are evaluated. The precedence and associativity of operators are obviously important in parsing compound expressions, but sequence points and lvalues are no less essential to understanding how a C program works.

5.1.1. Lvalues

An lvalue is an expression that designates an object. The simplest example is the name of a variable. The initial "L" in the term originally meant "left": because an lvalue designates an object, it can appear on the left side of an assignment operator, as in leftexpression = rightexpression.[*] Other expressionsthose that represent a value without designating an objectare called, by analogy, rvalues. An rvalue is an expression that can appear on the right side of an assignment operator, but not the left. Examples include constants and arithmetic expressions.

[*] The C standard acknowledges this etymology, but proposes that the L in lvalue be thought of as meaning "locator," because an lvalue always designates a location in memory. The standard steers clear of the term rvalue, preferring the phrase "not an lvalue."

An lvalue can always be resolved to the corresponding object's address, unless the object is a bit-field or a variable declared with the register storage class (see the section "Storage Class Specifiers" in Chapter 11). The operators that yield an lvalue include the subscript operator [ ] and the indirection operator *, as the examples in Table 5-2 illustrate (assume that array has been declared as an array and ptr as a pointer variable).

Table 5-2. Pointer and array expressions may be lvalues

Expression

Lvalue?

array[1]

Yes; an array element is an object with a location.

&array[1]

No; the location of the object is not an object with a location.

ptr

Yes; the pointer variable is an object with a location.

*ptr

Yes; what the pointer points to is also an object with a location.

ptr+1

No; the addition yields a new address value, but not an object.

*ptr+1

No; the addition yields a new arithmetic value, but not an object.


An object may be declared as constant. If this is the case, you can't use it on the left side of an expression, even though it is an lvalue, as the following example illustrates:

int a = 1;
const int b = 2, *ptr = &a;
b = 20;                // Error: b is declared as const int.
*ptr = 10;             // Error: ptr is declared as a pointer to const int.

In this example, the expressions a, b, ptr, and *ptr are all lvalues . However, b and *ptr are constant lvalues. Because ptr is declared as a pointer to const int, you cannot use it to modify the object it points to. For a full discussion of declarations, see Chapter 11.

The left operand of an assignment, as well as any operand of the increment and decrement operators, ++ and --, must be not only an lvalue, but also a modifiable lvalue. A modifiable lvalue is an lvalue that is not declared as a const-qualified type (see "Type Qualifiers" in Chapter 11), and that does not have an array type. If a modifiable lvalue designates an object with a structure or union type, none of its elements must be declared, directly or indirectly, as having a const-qualified type.

5.1.2. Side Effects and Sequence Points

In addition to yielding a value, the evaluation of an expression can result in other changes in the execution environment, called side effects. Examples of such changes include modifications of a variable's value, or of input or output streams.

During the execution of a program, there are determinate points at which all the side effects of a given expression have been completed, and no effects of the next expression have yet occurred. Such points in the program are called sequence points . Between two consecutive sequence points, partial expressions may be evaluated in any order. As a programmer, you must therefore remember not to modify any object more than once between two consecutive sequence points. An example:

int i = 1;         // OK.
i = i++;           // Wrong: two modifications of i; behavior is undefined.

Because the assignment and increment operations in the last statement may take place in either order, the resulting value of i is undefined. Similarly, in the expression f( )+g( ), where f( ) and g( ) are two functions, C does not specify which function call is performed first. It is up to you the programmer to make sure that the results of such an expression are not dependent on the order of evaluation. Another example:

int i = 0, array[ ] = { 0, 10, 20 };
// ...
array[i] = array[++i];          // Wrong: behavior undefined.
array[i] = array[i + 1]; ++i;   // OK: modifications separated by a sequence
                                // point.

The most important sequence points occur at the following positions:

  • After all the arguments in a function call have been evaluated, and before control passes to the statements in the function.

  • At the end of an expression which is not part of a larger expression. Such full expressions include the expression in an expression statement (see "Expression Statements" in Chapter 6), each of the three controlling expressions in a for statement, the condition of an if or while statement, the expression in a return statement, and initializers.

  • After the evaluation of the first operand of each of the following operators:

    • && (logical AND)

    • || (logical OR)

    • ?: (the conditional operator)

    • , (the comma operator)

Thus the expression ++i < 100 ? f(i++) : (i = 0) is permissible, as there is a sequence point between the first modification of i and whichever of the other two modifications is performed.

5.1.3. Operator Precedence and Associativity

An expression may contain several operators. In this case, the precedence of the operators determines which part of the expression is treated as the operand of each operator. For example, in keeping with the customary rules of arithmetic, the operators *, /, and % have higher precedence in an expression than the operators + and -. For example, the following expression:

a - b * c

is equivalent to a - (b * c). If you intend the operands to be grouped differently, you must use parentheses, thus:

(a - b) * c

If two operators in an expression have the same precedence, then their associativity determines whether they are grouped with operands in order from left to right, or from right to left. For example, arithmetic operators are associated with operands from left to right, and assignment operators from right to left, as shown in Table 5-3. Table 5-4 lists the precedence and associativity of all the C operators.

Table 5-3. Operator grouping

Expression

Associativity

Effective grouping

a / b % c

Left to right

(a / b) % c

a = b = c

Right to left

a = (b = c)


Table 5-4. Operator precedence and associativity

Precedence

Operators

Associativity

1.

Postfix operators :

[ ]  ( )  .  ->  ++  --
(type name){list}

Left to right

2.

Unary operators:

++  --
!  ~  +  -  *  &  sizeof

Right to left

3.

The cast operator: (type name)

Right to left

4.

Multiplicative operators: * / %

Left to right

5.

Additive operators: + -

Left to right

6.

Shift operators: << >>

Left to right

7.

Relational operators: < <= > >=

Left to right

8.

Equality operators: == !=

Left to right

9.

Bitwise AND: &

Left to right

10.

Bitwise exclusive OR: ^

Left to right

11.

Bitwise OR: |

Left to right

12.

Logical AND: &&

Left to right

13.

Logical OR: ||

Left to right

14.

The conditional operator: ? :

Right to left

15.

Assignment operators:

 =   +=   -=   *=
/=   %=   &=   ^=   |=  <<=  >>=

Right to left

16.

The comma operator: ,

Left to right


The last of the highest-precedence operators in Table 5-4, (type name){list}, is the newest, added in C99. It is described in "Compound literals," later in this chapter.

A few of the operator tokens appear twice in the table. To start with, the increment and decrement operators , ++ and --, have a higher precedence when used as postfix operators (as in the expression x++) than the same tokens when used as prefix operators (as in ++x).

Furthermore, the tokens +, -, *, and & represent both unary operators that is, operators that work on a single operandand binary operators , or operators that connect two operands. For example, * with one operand is the indirection operator, and with two operands, it is the multiplication sign. In each of these cases, the unary operator has higher precedence than the binary operator. For example, the expression *ptr1 * *ptr2 is equivalent to (*ptr1) * (*ptr2).


Previous Page
Next Page