— T.S. Eliot
Errors, defects, bugs, blunders — when we talk about software-related errors, we often use terms loosely and synonymously — but there are differences. For instance, in his book “The Design of Everyday Things“, Donald A. Norman makes a clear distinction between “mistakes” and “slips”:
“Errors come in several forms. Two fundamental categories are slips and mistakes. Slips result from automatic behavior, when subconscious actions that are intended to satisfy our goals get waylaid en route. Mistakes result from conscious deliberations.”
In short: mistakes are the result of faulty ideas whereas slips are errors made when implementing an idea. Usually, slips are not just easy to make, but also easy to fix. Fixing mistakes is typically much harder.
One of the easiest slips to make in C/C++ is to inadvertently do a boolean test on an assignment expression:
1 2 3 4 5 |
if (a = b) { // Oops! Should have been a == b. ... } |
which is equivalent to:
1 2 3 4 5 |
if ((a = b) != 0) { ... } |
While in some rare cases this is exactly what the developer had in mind, in 99% of all cases it’s not. Hence, boolean-testing assignments is explicitly banned by many C/C++ coding standards and frowned-upon by most developers.
But what’s all the fuzz about, you might ask. If an unlucky developer forgets to type the second ‘=’, any decent 21st century compiler surely generates a warning, doesn’t it? Well, the answer is, as we shall see, both, yes and no.
If you compile the example above with GCC (I’ve tried version 5.4.0) using options ‘-W -Wall’, you do get a warning:
warning: suggest parentheses around assignment used as truth value
GCC’s reasoning is this: if developers really wanted to truth test the assignment (there are still people out there who do, as strange as this may sound), they need to put an extra pair of parentheses around the assignment, to show their intend:
1 2 3 4 5 |
if ((a = b)) { // Warning is gone. ... } |
Requiring an extra set of parentheses seems to be a neat idea, but it’s the devil in disguise. For one thing, it reminds me of Sledge Hammer saying “Trust me, I know what I’m doing” (which was usually entailed by disaster), for another, it doesn’t work reliably. In order to explain, I first need to put the same slip in a slightly more complicated expression:
1 2 3 4 5 |
if (a == b && c = d) { // Should be c == d. ... } |
In this case, you not just get a warning, your compiler will refuse to compile this code. Why? According to C’s precedence rules, the assignment operator has lower priority than the ‘&&’ operator, which means that the code is equivalent to
1 2 3 4 5 |
if (((a == b) && c) = d) { // Should be c == d. ... } |
The C language standard says that the result of an ‘&&’ expression is a so-called “rvalue” and an rvalue is more or less read-only. Thus, assigning ‘d’ to it is just not possible and GCC is right when it barks:
error: lvalue required as left operand of assignment
A slip that doesn’t compile is a kind slip, you might think, but read on. We only got lucky by accident, so to speak.
Many coding standards, like MISRA, for instance, require that you put parentheses around subexpressions to clearly show what precedence you have in mind, instead of relying on obscure operator precedence rules. Hence, instead of
1 2 3 4 5 |
if (a == b && c == d) // violates MISRA-C 2012, Rule 12.1: // "The precedence of operators within // expressions should be made explicit" |
you have to write
1 2 3 |
if ((a == b) && (c == d)) // MISRA compliant |
MISRA exists to make coding errors unlikely, but if a MISRA-abiding developer forgets the second ‘=’, he’s out of luck, at least if he’s using GCC:
1 2 3 |
if ((a == b) && (c = d)) // MISRA compliant, but a slip anyway... |
Now the devil reveals himself: since the parentheses are properly placed, there is no attempt to assign to an rvalue, so there won’t be a compile-time error and because of GCC’s “parentheses feature” mentioned above, GCC doesn’t issue a warning, either.
Early in my career as a software developer, I read the aforementioned book “The Design of Everyday Things” and I believe it left a mark on me. One of the book’s unforgettable key lesson is this:
“When you have trouble with things — whether it’s figuring out whether to push or pull a door or the arbitrary vagaries of the modern computer and electronics industry — it’s not your fault. Don’t blame yourself: blame the designer.”
GCC’s “extra parentheses” feature is far from neat design — it’s rather bad design that doesn’t work in all contexts and gives developers a false sense of security. It was deliberately put in and correctly implemented but the idea was wrong from the outset. Thus, it’s not a slip, but obviously a mistake.