5. Operator Analysis

  1. 5.1. Order of evaluation
  2. 5.2. Operator precedence
  3. 5.3. Floating point equality
  4. 5.4. Operand of sizeof

5.1. Order of evaluation

The ISO C standard specifies certain points in the expression syntax at which all prior expressions encountered are guaranteed to have been evaluated. These positions are called sequence points and occur:

  • after the arguments and function expression of a function call have been evaluated but before the call itself;

  • after the first operand of a logical &&, or || operator;

  • after the first operand of the conditional operator, ?:;

  • after the first operand of the comma operator;

  • at the end of any full expression (a full expression may take one of the following forms: an initialiser; the expression in an expression statement; the controlling expression in an if, while, do or switch statement; each of the three optional expressions of a for statement; or the optional expression of a return statement).

Between two sequence points however, the order in which the operands of an operator are evaluated, and the order in which side effects take place is unspecified - any order which conforms to the operator precedence rules above is permitted. For example:

var = i + arr[ i++ ] ;

may evaluate to different values on different machines, depending on which argument of the + operator is evaluated first. The checker can detect expressions which depend on the order of evaluation of sub-expressions between sequence points and these are flagged as errors or warnings when the variable analysis is enabled.

5.2. Operator precedence

The ISO C standard section 6.3, provides a set of rules governing the order in which operators within expressions should be applied. These rules are said to specify the operator precedence and are summarised in the table over the page. Operators on the same line have the same precedence and the rows are in order of decreasing precedence. Note that the unary +, -, * and & operators have higher precedence than the binary forms and thus appear higher in the table.

The precedence of operators is not always intuitive and often leads to unexpected results when expressions are evaluated. A particularly common example is to write:

if ( var & TEST == 1) { ...
}
else { ...

assuming that the control expression will be evaluated as:

( ( var & TEST ) == 1 )

However, the == operator has a higher precedence than the bitwise & operator and the control expression is evaluated as:

( var & ( TEST == 1 ) )

which in general will give a different result.

OperatorsPrecedence
function call() [] -> . ++(postfix) --(postfix)highest
! ~ ++ -- + - * & (type) sizeof
* / %
+(binary) -(binary)
<< >>
< <= > >=
== !=
&
^
|
&&
||
?:
= += -= *= /= %= &= ^= |= <<= >>=
,lowest
Table 2. ISO C Rules for Operator Precedence

The TenDRA C checker can be configured to flag expressions containing certain operators whose precedence is commonly confused, namely:

  • && versus ||

  • << and >> versus + and -

  • & versus == != < > <= >= + and -

  • ^ versus & == |= < > <= >= + and -

  • | versus ^ & == |= < > <= >= + and -

The check is switched off by default. The the directive:

#pragma TenDRA operator precedence analysis on

can be used to enable a check for expressions where the operator precedence is not necessarily what might be expected. The intended precedence can be clarified by means of explicit parentheses. The precedence levels checked are as follows:

  • && versus ||.

  • << and >> versus binary + and -.

  • Binary & versus binary +, -, ==, !=, >, >=, < and <=.

  • ^ versus binary &, +, -, ==, !=, >, >=, < and <=.

  • | versus binary ^, &, +, -, ==, !=, >, >=, < and <=.

Also checked are expressions such as a < b < c which do not have their normal mathematical meaning. For example, in:

d = a << b + c ;	// precedence is a << ( b + c )

the precedence is counter-intuitive, although strangely enough, it isn't in:

cout << b + c ;		// precedence is cout << ( b + c )

Other dubious arithmetic operations can be checked for using the directive:

#pragma TenDRA integer operator analysis on

This includes checks for operations, such as division by a negative value, which are implementation dependent, and those such as testing whether an unsigned value is less than zero, which serve no purpose. Similarly the directive:

#pragma TenDRA++ pointer operator analysis on

checks for dubious pointer operations. This includes very simple bounds checking for arrays and checking that only the simple literal 0 is used in null pointer constants:

char *p = 1 - 1 ;	// valid, but weird

The directive:

#pragma TenDRA integer overflow analysis on

is used to control the treatment of overflows in the evaluation of integer constant expressions. This includes the detection of division by zero.

5.3. Floating point equality

Due to the rounding errors that occur in the handling of floating point values, comparison for equality between two floating point values is a hazardous and unpredictable operation. Tests for equality of two floating point numbers are controlled by:

#pragma TenDRA floating equality permit

where permit is allow, warning or disallow. By default the check is switched off.

5.4. Operand of sizeof

According to the ISO C standard, section 6.3.3.4, the operand of the sizeof operator is not itself evaluated. If the operand has any side-effects these will not occur. When the variable analysis is enabled, the checker detects the use of expressions with side-effects in the operand of the sizeof operator.