we find that, since the expression only involves the assignment operator, precedence does
not help us understand how the operands are grouped. So which happens first, the
assignment of c to b, or the assignment of b to a? In the first case, a would be left with the
value 2, and in the second case, a would end up as 1.
All assignment-operators have right associativity. The associativity protocol says that this
means the rightmost operation in the expression is evaluated first, and evaluation proceeds
from right to left. Thus, the value of c is assigned to b. Then the value of b is stored in a. a
gets the value 2. Similarly, for operators with left associativity (such as the bitwise and's
and or 's), the operands are grouped from left to right.
The only use of associativity is to disambiguate an expression of two or more equal-
precedence operators. In fact, you'll note that all operators which share the same precedence
level also share the same associativity. They have to, or else the expression evaluation
would still be ambiguous. If you need to take associativity into account to figure out the
value of an expression, it's usually better to rewrite the expression into two expressions, or
to use parentheses.
The order in which things happen in C is defined for some things and not for others. The order of
precedence and association are well-defined. However, the order of expression evaluation is mostly
unspecified (the special term defined in the previous chapter) to allow compiler-writers the maximum
leeway to generate the fastest code. We say "mostly" because the order is defined for && and || and
a couple of other operators. These two evaluate their operands in a strict left-to-right order, stopping
when the result is known. However, the order of evaluation of the arguments in a function call is
another unspecified order.
The Early Bug gets() the Internet Worm
The problems in C are not confined to just the language. Some routines in the standard library have
unsafe semantics. This was dramatically demonstrated in November 1988 by the worm program that
wriggled through thousands of machines on the Internet network. When the smoke had cleared and the
investigations were complete, it was determined that one way the worm had propagated was through a
weakness in the finger daemon, which accepts queries over the network about who is currently logged
in. The finger daemon, in.fingerd, used the standard I/O routine gets().
The nominal task of gets() is to read in a string from a stream. The caller tells it where to put the
incoming characters. But gets() does not check the buffer space; in fact, it can't check the buffer
space. If the caller provides a pointer to the stack, and more input than buffer space, gets() will
happily overwrite the stack. The finger daemon contained the code: