Chapter 6. Expressions and Operators

This chapter discusses the various expressions and operators available in C. The sections describing expressions and operators are presented roughly in order of precedence.

Precedence and Associativity Rules in C

Operators in C have rules of precedence and associativity that determine how expressions are evaluated. Table 6-2, lists the operators and indicates the precedence and associativity of each. Within each row, the operators have the same precedence. Parentheses can be used to override these rules.

Table 6-1, shows some simple examples of precedence and associativity.

Table 6-1. Precedence and Associativity Examples

Expression

Results

Comments

3 + 2 * 5

13

Multiplication is done before addition.

3 + (2 * 5)

13

Parentheses follow the precedence rules, but clarify the expression for the reader.

(3 + 2) * 5

25

Parentheses override the precedence rules.

TRUE || TRUE && FALSE

1 (true)

Logical AND has higher priority than logical OR.

TRUE || (TRUE && FALSE)

1 (true)

Parentheses follow the precedence rules, but clarify the expression for the reader.

(TRUE || TRUE) && FALSE

0 (false)

Parentheses override the precedence rules.

Except as indicated by the syntax or specified explicitly in this chapter, the order of evaluation of expressions, as well as the order in which side-effects take place, is unspecified. The compiler can arbitrarily rearrange expressions involving a commutative and associative operator (*, +, &, |, ^).

Table 6-2, lists the precedence and associativity of all operators.

Table 6-2. Operator Precedence and Associativity

Tokens (From High to Low Priority)

Operators

Class

Associativity

Identifiers, constants, string literal, parenthesized expression

Primary expression

Primary

 

() [] -> .

Function calls, subscripting, indirect selection, direct selection

Postfix

L-R

++ --

Increment, decrement (postfix)

Postfix

L-R

++ --

Increment, decrement (prefix)

Prefix

R-L

! ~ + - & sizeof *

Logical and bitwise NOT, unary plus and minus, address, size, indirection

Unary

R-L

(type)

Cast

Unary

R-L

* / %

Multiplicative

Binary

L-R

+ -

Additive

Binary

L-R

<< >>

Left shift, right shift

Binary

L-R

< <= > >=

Relational comparisons

Binary

L-R

== !=

Equality comparisons

Binary

L-R

&

Bitwise and

Binary

L-R

^

Bitwise exclusive or

Binary

L-R

|

Bitwise inclusive or

Binary

L-R

&&

Logical and

Binary

L-R

||

Logical or

Binary

L-R

? :

conditional

Ternary

R-L

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

Assignment

Binary

R-L

,

Comma

Binary

L-R


Primary Expressions

The following are all considered "primary expressions:"

Identifiers 

An identifier referring to an object is an lvalue. An identifier referring to a function is a function designator. lvalues and function designators are discussed in "Conversion of lvalues and Function Designators" on page 59.

Constants 

A constant's type is determined by its form and value, as described in “Constants” in Chapter 3.

String literals 

A string literal's type is "array of char," subject to modification, as described in “Conversions of Characters and Integers” in Chapter 5.

Parenthesized expressions 

A parenthesized expression's type and value are identical to those of the unparenthesized expression. The presence of parentheses does not affect whether the expression is an lvalue, rvalue, or function designator. For information on expressions, see "Constant Expressions" on page 79.

Postfix Expressions

Postfix expressions involving ., ->, subscripting, and function calls associate left to right. The syntax for these expressions is as follows:

postfix-expression: 

primary-expression

postfix-expression [expression]

postfix-expression (argument-expression-list opt)

postfix-expression. identifier

postfix-expression -> identifier

postfix-expression ++

postfix-expression - -

argument-expression-list: 

argument-expression

argument-expression-list, argument-expression

Subscripts

A postfix expression followed by an expression in square brackets is a subscript. Usually, the postfix expression has type "pointer to <type>", the expression within the square brackets has type int, and the type of the result is <type>. However, it is equally valid if the types of the postfix expression and the expression in brackets are reversed. This is because the expression E1[E2] is identical (by definition) to *((E1)+(E2)). Because addition is commutative, E1 and E2 can be interchanged.

You can find more information on this notation in the discussions on identifiers and in the discussion of the * and + operators (in “Unary Operators”, and “Additive Operators”), respectively.

Function Calls

The syntax of function call postfix expressions is as follows:

postfix-expression (argument-expression-listopt)

argument-expression-list: 

argument-expression

argument-expression-list, argument-expression

A function call is a postfix expression followed by parentheses containing a (possibly empty) comma-separated list of expressions that are the arguments to the function. The postfix expression must be of type "function returning <type>." The result of the function call is of type <type>, and is not an lvalue.

The behavior of function calls is as follows:

  • If the function call consists solely of a previously unseen identifier foo, the call produces an implicit declaration as if, in the innermost block containing the call, the following declaration had appeared:

    extern int foo(); 

  • If a corresponding function prototype that specifies a type for the argument being evaluated is in force, an attempt is made to convert the argument to that type.

  • If the number of arguments does not agree with the number of parameters specified in the prototype, the behavior is undefined.

  • If the type returned by the function as specified in the prototype does not agree with the type derived from the expression containing the called function, the behavior is undefined. Such a scenario may occur for an external function declared with conflicting prototypes in different files.

  • If no corresponding prototype is in scope or if the argument is in the variable argument section of a prototype that ends in ellipses (...), the argument is converted according to the following default argument promotions:

    • Type float is converted to double.

    • Array and function names are converted to corresponding pointers.

    • When using traditional C, types unsigned short and unsigned char are converted to unsigned int, and types signed short and signed char are converted to signed int.

    • When using ANSI C, types short and char, whether signed or unsigned, are converted to int.

  • In preparing for the call to a function, a copy is made of each actual argument. Thus, all argument passing in C is strictly by value. A function can change the values of its parameters, but these changes cannot affect the values of the actual arguments. It is possible to pass a pointer on the understanding that the function can change the value of the object to which the pointer points. (Arguments that are array names can be changed as well, because these arguments are converted to pointer expressions.)

  • Because the order of evaluation of arguments is unspecified, side effects may be delayed until the next sequence point, which occurs at the point of the actual call and after all arguments have been evaluated. (For example, in the function call func(foo++), the incrementation of foo may be delayed.)

  • Recursive calls to any function are permitted.

Silicon Graphics recommends consistent use of prototypes for function declarations and definitions. Do not mix prototyped and nonprototyped function declarations and definitions. Even though the language allows it, never call functions before you declare them. This results in an implicit nonprototyped declaration that may be incompatible with the function definition.

Structure and Union References

A postfix expression followed by a dot followed by an identifier denotes a structure or union reference. The syntax is as follows:

postfix-expression. identifier

The postfix expression must be a structure or a union, and the identifier must name a member of the structure or union. The value is the value of the named member of the structure or union, and is an lvalue if the first expression is an lvalue.The result has the type of the indicated member and the qualifiers of the structure or union.

Indirect Structure and Union References

A postfix-expression followed by an arrow (built from - and >) followed by an identifier is an indirect structure or union reference. The syntax is as follows:

postfix-expression -> identifier

The postfix expression must be a pointer to a structure or a union, and the identifier must name a member of that structure or union. The result is an lvalue referring to the named member of the structure or union to which the postfix expression points. The result has the type of the selected member, and the qualifiers of the structure or union to which the postfix expression points. Thus, the expression E1->MOS is the same as (*E1).MOS.

Structures and unions are discussed in “Structure and Union Declarations” in Chapter 7.

postfix ++ and postfix - -

The syntax of postfix ++ and postfix -- is as follows:

postfix-expression ++
postfix-expression - -

When postfix ++ is applied to a modifiable lvalue, the result is the value of the object referred to by the lvalue. After the result is noted, the object is incremented by 1 (one). See the discussions in “Additive Operators”, and “Assignment Operators”, for information on conversions. The type of the result is the same as the type of the lvalue expression. The result is not an lvalue.

When postfix -- is applied to a modifiable lvalue, the result is the value of the object referred to by the lvalue. After the result is noted, the object is decremented by 1 (one). See the discussions in “Additive Operators”, and “Assignment Operators”, for information on conversions. The type of the result is the same as the type of the lvalue expression. The result is not an lvalue.

For both postfix ++ and postfix -- operators, updating the stored value of the operand may be delayed until the next sequence point.

Unary Operators

Expressions with unary operators associate from right to left. The syntax for unary operators is as follows:

unary-expression: 

postfix-expression

++ unary-expression

- - unary-expression

unary-operator cast-expression

sizeof unary-expression

sizeof (type-name)

unary-operator: one of 

* & - ! ~ +

Except as noted, the operand of a unary operator must have arithmetic type.

Address-of and Indirection Operators

The unary * operator means "indirection"; the cast expression must be a pointer, and the result is either an lvalue referring to the object to which the expression points, or a function designator. If the type of the expression is "pointer to <type>", the type of the result is <type>.

The operand of the unary & operator can be either a function designator or an lvalue that designates an object. If it is an lvalue, the object it designates cannot be a bitfield, and it cannot be declared with the storage class register. The result of the unary & operator is a pointer to the object or function referred to by the lvalue or function designator. If the type of the lvalue is <type>, the type of the result is "pointer to <type>".

Unary + and - Operators

The result of the unary - operator is the negative of its operand. The integral promotions are performed on the operand, and the result has the promoted type and the value of the negative of the operand. Negation of unsigned quantities is analogous to subtracting the value from 2n, where n is the number of bits in the promoted type.

The unary + operator exists only in ANSI C. The integral promotions are used to convert the operand. The result has the promoted type and the value of the operand.

Unary ! and ~ Operators

The result of the logical negation operator ! is 1 if the value of its operand is zero, and 0 if the value of its operand is nonzero. The type of the result is int. The logical negation operator is applicable to any arithmetic type and to pointers.

The ~ operator (bitwise not) yields the one's complement of its operand. The usual arithmetic conversions are performed. The type of the operand must be integral.

Prefix ++ and - - Operators

The prefix operators ++ and -- increment and decrement their operands. Their syntax is as follows:

++ unary-expression
- - unary-expression

The object referred to by the modifiable lvalue operand of prefix ++ is incremented. The expression value is the new value of the operand but is not an lvalue. The expression ++x is equivalent to x += 1. See the discussions in “Additive Operators”, and “Assignment Operators”, for information on conversions.

The prefix -- decrements its lvalue operand in the same way that prefix ++ increments it.

sizeof Unary Operator

The sizeof operator yields the size in bytes of its operand. The size of a char is 1 (one). Its major use is in communication with routines such as storage allocators and I/O systems. The syntax of the sizeof operator is as follows:

sizeof unary-expression
sizeof (type-name)

The operand of sizeof cannot have function or incomplete type, or be an lvalue that denotes a bitfield. It can be an object or a parenthesized type name. In traditional C, the type of the result is unsigned. In ANSI C, the type of the result is size_t, which is defined in <stddef.h> as unsigned int (in -o32 and -n32 modes) or as unsigned long (in -64 mode). The result is a constant and can be used anywhere a constant is required.

When applied to an array, sizeof returns the total number of bytes in the array. The size is determined from the declaration of the object in the unary expression. For variable length array types, the result is not a constant expression and is computed at run time.

The sizeof operator can also be applied to a parenthesized type name. In that case, it yields the size in bytes of an object of the indicated type.

When sizeof is applied to an aggregate, the result includes space used for padding, if any.

Cast Operators

A cast expression preceded by a parenthesized type name causes the value of the expression to convert to the indicated type. This construction is called a cast. Type names are discussed in “Type Names” in Chapter 7. The syntax of a cast expression is as follows:

cast-expression: 

unary-expression

(type-name) cast-expression

The type name specifies a scalar type or void, and the operand has scalar type. Because a cast does not yield an lvalue, the effect of qualifiers attached to the type name is inconsequential.

When an arithmetic value is cast to a pointer, and vice versa, the appropriate number of bits are simply copied unchanged from one type of value to the other. Be aware of the possible truncation of pointer values in 64-bit mode compilation, when a pointer value is converted to an (unsigned) int.

Multiplicative Operators

The multiplicative operators *, /, and % group from left to right. The usual arithmetic conversions are performed. The following is the syntax for the multiplicative operators:

multiplicative expression: 

cast-expression

multiplicative-expression*cast-expression

multiplicative-expression/cast-expression

multiplicative-expression%cast-expression

Operands of * and / must have arithmetic type. Operands of % must have integral type.

The binary * operator indicates multiplication, and its result is the product of the operands.

The binary / operator indicates division of the first operator (dividend) by the second (divisor). If the operands are integral and the value of the divisor is 0, SIGTRAP is signalled. Integral division results in the integer quotient whose magnitude is less than or equal to that of the true quotient, and with the same sign.

The binary % operator yields the remainder from the division of the first expression (dividend) by the second (divisor). The operands must be integral. The remainder has the same sign as the dividend, so that the equality below is true when the divisor is nonzero:

(dividend / divisor) * divisor + dividend % divisor == dividend

If the value of the divisor is 0, SIGTRAP is signalled.

Additive Operators

The additive operators + and - associate from left to right. The usual arithmetic conversions are performed.The syntax for the additive operators is as follows:

additive-expression: 

multiplicative-expression

additive-expression+multiplicative-expression

additive-expression-multiplicative-expression

In addition to arithmetic types, the following type combinations are acceptable for additive expressions:

  • For addition, one operand is a pointer to an object type and the other operand is an integral type.

  • For subtraction,

    • Both operands are pointers to qualified or unqualified versions of compatible object types.

    • The left operand is a pointer to an object type, and the right operand has integral type.

The result of the + operator is the sum of the operands. The result of the - operator is the difference of the operands.

When an operand of integral type is added to or subtracted from a pointer to an object type, the integral operand is first converted to an address offset by multiplying it by the length of the object to which the pointer points. The result is a pointer of the same type as the original pointer.

For instance, suppose a has type "array of <object>", and p has type "pointer to <object>" and points to the initial element of a. Then the result of p + n, where n is an integral operand, is the same as &a[n].

If two pointers to objects of the same type are subtracted, the result is converted (by division by the length of the object) to an integral quantity representing the number of objects separating them. Unless the pointers point to objects in the same array, the result is undefined. The actual type of the result is int in traditional C, and ptrdiff_t (defined in <stddef.h> as int in -o32 and -n32 modes and as long in -64 mode) in ANSI C.

Shift Operators

The shift operators << and >> associate from left to right. Each operand must be an integral type. The integral promotions are performed on each operand. The syntax is as follows:

shift-expression: 

additive-expression

shift-expression<<additive-expression

shift-expression>>additive-expression

The type of the result is that of the promoted left operand. If the right operand is negative, greater than, or equal to the length in bits of the promoted left operand, the result is undefined.

The value of E1 << E2 is E1 (interpreted as a bit pattern) left-shifted E2 bits. Vacated bits are filled with zeros.

The value of E1 >> E2 is E1 right-shifted E2 bit positions. If E1 is unsigned, or if it is signed and its value is nonnegative, vacated bits are filled with zeros. If E1 is signed and its value is negative, vacated bits are filled with ones.

Relational Operators

The relational operators associate from left to right. The syntax is as follows:

relational-expression: 

shift-expression

relational-expression<shift-expression

relational-expression >shift-expression

relational-expression<=shift-expression

relational-expression>=shift-expression

The operators < (less than), > (greater than), <= (less than or equal to), and >= (greater than or equal to) all yield a result of type int with the value 0 if the specified relation is false and 1 if it is true.

The operands must be one of the following:

  • Both arithmetic, in which case the usual arithmetic conversions are performed on them

  • Both pointers to qualified or unqualified versions of compatible object types

  • Both pointers to qualified or unqualified versions of compatible incomplete types

When two pointers are compared, the result depends on the relative locations in the address space of the pointed-to objects. Pointer comparison is portable only when the pointers point to objects in the same aggregate. In particular, no correlation is guaranteed between the order in which objects are declared and their resulting addresses.

Equality Operators

The == (equal to) and the != (not equal to) operators are exactly analogous to the relational operators except for their lower precedence. (For example, a < b == c < d is 1 whenever a < b and c < d have the same truth value.) The syntax of the equality operators is as follows:

equality-expression: 

relational-expression

equality-expression==relational-expression

equality-expression!=relational-expression

The operands must be one of the following:

  • Both arithmetic, in which case the usual arithmetic conversions are performed on them

  • Both pointers to qualified or unqualified versions of compatible types

  • A pointer to an object or incomplete type, and a pointer to qualified or unqualified void type

  • A pointer and a null pointer constant

The semantics detailed in “Relational Operators”, apply if the operands have types suitable for those operators. Combinations of other operands have the following behavior:

  • Two null pointers to object or incomplete types are equal. If two pointers to such types are equal, they either are null, point to the same object, or point to one object beyond the end of an array of such objects.

  • Two pointers to the same function are equal, as are two null function pointers. Two function pointers that are equal are either both null or both point to the same function.

Bitwise AND Operator

Each operand of the bitwise AND operator must have integral type. The usual arithmetic conversions are performed. The syntax is as follows:

AND-expression: 

equality-expression

AND-expression & equality-expression

The result is the bitwise AND function of the operands, that is, each bit in the result is 0 unless the corresponding bit in each of the two operands is 1.

Bitwise Exclusive OR Operator

Each operand of the bitwise exclusive OR operator must have integral type. The usual arithmetic conversions are performed. The syntax is as follows:

exclusive-OR-expression: 

AND-expression

exclusive-OR-expression ^ AND-expression

The result has type int, long, or long long, and the value is the bitwise exclusive OR function of the operands. That is, each bit in the result is 0 unless the corresponding bit in one of the operands is 1, and the corresponding bit in the other operand is 0.

Bitwise Inclusive OR Operator

Each operand of the bitwise inclusive OR operator must have integral type. The usual arithmetic conversions are performed. The syntax is as follows:

inclusive-OR-expression: 

exclusive-OR-expression

inclusive-OR-expression|exclusive-OR-expression

The result has type int, long, or long long, and the value is the bitwise inclusive OR function of the operands. That is, each bit in the result is 0 unless the corresponding bit in at least one of the operands is 1.

Logical AND Operator

Each of the operands of the logical AND operator must have scalar type. The && operator associates left to right. The syntax is as follows:

logical-AND-expression: 

inclusive-OR-expression

logical-AND-expression &&inclusive-OR-expression

The result has type int. If neither of the operands evaluates to 0, the result has a value of 1. Otherwise it has a value of 0.

Unlike &, && guarantees left-to-right evaluation; moreover, the second operand is not evaluated if the first operand evaluates to zero. There is a sequence point after the evaluation of the first operand.

Logical OR Operator

Each of the operands of the logical OR operator must have scalar type. The || operator associates left to right. The syntax is as follows:

logical-OR-expression: 

logical-AND-expression

logical-OR-expression||logical-AND-expression

The result has type int. If either of the operands evaluates to one, the result has a value of 1. Otherwise it has a value of 0.

Unlike |, || guarantees left to right evaluation; moreover, the second operand is not evaluated unless the first operand evaluates to zero. A sequence point occurs after the evaluation of the first operand.

Conditional Operator

Conditional expressions associate from right to left. The syntax is as follows:

conditional-expression: 

logical-OR-expression

logical-OR-expression?expression : conditional-expression

The type of the first operand must be scalar. Only certain combinations of types are allowed for the second and third operands. These combinations are listed below, along with the type of result that the combination yields:

  • Both can be arithmetic types. In this case, the usual arithmetic conversions are performed on them to derive a common type, which is the type of the result.

  • Both are compatible structure or union objects. The result has the same type as the operands.

  • Both are void. The type of the result is void.

  • One is a pointer, and the other a null pointer constant. The type of the result is the type of the nonconstant pointer.

  • One is a pointer to void, and the other is a pointer to an object or incomplete type. The second operand is converted to a pointer to void. The result is also a pointer to void.

  • Both are pointers to qualified or unqualified versions of compatible types. The result has a type compatible with each, qualified with all the qualifiers of the types pointed to by both operands.

Evaluation of the conditional operator proceeds as follows:

  • The first expression is evaluated, after which a sequence point occurs.

  • If the value of the first expression is nonzero, the result is the value of the second operand.

  • If the value of the first expression is zero, the result is the value of the third operand.

  • Only one of the second and third operands is evaluated.

Assignment Operators

All assignment operators associate from right to left. The syntax is as follows:

assignment-expression:  

conditional-expression

unary-expression assignment-operator assignment-expression

assignment operator: one of 

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

Assignment operators require a modifiable lvalue as their left operand. The type of an assignment expression is that of its unqualified left operand. The result is not an lvalue. Its value is the value stored in the left operand after the assignment, but the actual update of the stored value may be delayed until the next sequence point.

The order of evaluation of the operands is unspecified.

Assignment Using = (Simple Assignment)

The operands permissible in simple assignment must obey one of the following:

  • Both have arithmetic type or are compatible structure or union types.

  • Both are pointers, and the type pointed to by the left has all of the qualifiers of the type pointed to by the right.

  • One is a pointer to an object or incomplete type, and the other is a pointer to void. The type pointed to by the left must have all of the qualifiers of the type pointed to by the right.

  • The left operand is a pointer, and the right is a null pointer constant.

In simple assignment, the value of the right operand is converted to the type of the assignment expression and replaces the value of the object referred to by the left operand. If the value being stored is accessed by another object that overlaps it, the behavior is undefined unless the overlap is exact and the types of the two objects are compatible.

Compound Assignment

For the operators += and -=, either both operators must have arithmetic types, or the left operand must be a pointer and the right an operand integral. In the latter case, the right operand is converted as explained in “Additive Operators”. For all other operators, each operand must have arithmetic type consistent with those allowed for the corresponding binary operator.

The expression E1 op = E2is equivalent to the expression E1 = E1 op E2, except that in the former, E1 is evaluated only once.

Comma Operator

A pair of expressions separated by a comma is evaluated left to right, and the value of the left expression is discarded. This operator associates left to right. The syntax of the comma operator is as follows:

expression: 

assignment-expression

expression, assignment-expression

The type and value of the result are the type and value of the right operand. In contexts where the comma is given a special meaning, the comma operator as described in this section can appear only in parentheses. Two such contexts are lists of actual arguments to functions (described in “Primary Expressions”) and lists of initializers (see “Initialization” in Chapter 7). For example, the following code has three arguments, the second of which has the value 5:

f(a, (t=3, t+2), c) 

Constant Expressions

A constant expression can be used any place a constant can be used. The syntax is as follows:

constant-expression: 

conditional-expression

A constant expression cannot contain assignment, increment, decrement, function-call, or comma operators. It must evaluate to a constant that is in the range of representable values for its type. Otherwise, the semantic rules for the evaluation of nonconstant expressions apply.

Constant expressions are separated into three classes:

  • An integral constant expression has integral type and is restricted to operands that are integral constants, sizeof expressions (whose operands do not have variable length array type or a parenthesized name of such a type), and floating constants that are the immediate operands of integral casts.

  • An arithmetic constant expression has arithmetic type and is restricted to operands that are arithmetic constants, and sizeof expressions (whose operands do not have variable length array type or a parenthesized name of such a type). Cast expressions in arithmetic constant expressions can convert only between arithmetic types.

  • An address constant is a pointer to an lvalue designating an object of static storage duration, or a pointer to a function designator. It can be created explicitly or implicitly, as long as no attempt is made to access an object value.

Either address or arithmetic constant expressions can be used in initializers. In addition, initializers can contain null pointer constants and address constants (for object types), and plus or minus integral constant expressions.

Integer and Floating Point Exceptions

The following are a few points to keep in mind about integer and floating point exceptions:

  • Integer divide-by-zero results in a trap. Other integer exception conditions are ignored.

  • Silicon Graphics' floating point conforms to the IEEE standard. Floating point exceptions are ignored by default, yielding the default IEEE results of infinity for divide-by-zero and overflow, not-a-number for invalid operations, and zero for underflow.

  • You can gain control over these exceptions and their results most easily by <_newline> using the Silicon Graphics IEEE floating point exception handler package (see the handle_sigfpes(3c) reference page).

  • You can also control these exceptions by implementing your own handler and appropriately initializing the floating point unit (see the fpc(3c) reference page).