Logical Operators, Bitwise Operators, and Sequence Points

Randall Maas 5/20/2010 12:49:58 PM

From time to time, I read C code that uses the bitwise | operator when the logical || operator is more appropriate. The code might look a bit like:

int  Pass = A | B | C;

or:

if (!A|!B|!C)
   Pass = 0;
 else
   Pass = 1;

(There are similar cases with the & and && operators, but these tend to come up less frequently).

Sometimes this is the right construction. This might be the case if all of the operands - A, B, C, etc - don't have any side effects, and are quick to evaluate. And you are checking the resulting bit pattern. And you are not storing the result in a signed type. (These are not strict rules, just typifying guidelines). If the implementer intended to use the binary operator, I recommend that he include such a statement in the comments, as well as a description of what is going on.

What is the problem then? The issue is that all of the operands are evaluated. This is a problem if you (implicitly) should be stopping at the first pass (or first failure). If these operands are calls to expensive operations, you are incurring the expense unnecessarily - in the embedded world the threshold for 'expensive' can be quite low. More importantly, it is common for the operands to have a side effects. If you are doing operations, and the first one fails, it is almost always a terrible idea to perform subsequent operations in the sequence.

And, to make matters worse, I'm pretty convinced that there are compilers that will evaluate the operands in "the wrong order," different from what the implementer expected. Especially when the optimizer is used. (The age of the compiler matters too, as recent C standards have tried to specify portions of the evaluation order). The typical example that this occurs is when the operands include modifications to a variable (a *= 3, b++, c>>=7). It is undefined when the changes to these variables are applied.

Instead the logical || operator is usually the best choice. It skips the rest of the sequence on the first true (non-zero) operand. (With the logical && operator, it skips the rest of the sequence on the first false - zero - operand). In complicated words, || is divided up into sequence points.

For example the following bit of contrived code, using the bitwise operator, would do something undefined to memory if the memory allocation failed:

if (!AllocateMemory() | !WriteMemory())
   Pass = 0;
 else
   Pass = 1;
FreeMemory();

whereas the following code would not:

if (!AllocateMemory() || !WriteMemory())
   Pass = 0;
 else
   Pass = 1;
FreeMemory();

(From here we could discuss interesting things like how to use the || operator to do error checking and handling, how C#'s version of the | operator is different and what this may mean, and some learned lessons in language design.)