Showing posts with label c language tutorials. Show all posts
Showing posts with label c language tutorials. Show all posts

Friday, 19 August 2011

section 2.12: Precedence and Order of Evaluation


section 2.12: Precedence and Order of Evaluation

Note that precedence is not the same thing as order of evaluation. Precedence determines how an expression is parsed, and it has an influence on the order in which parts of it are evaluated, but the influence isn't as strong as you'd think. Precedence says that in the expression
        1 + 2 * 3
the multiplication happens before the addition. But if we have several function calls, such as
        f() + g() * h()
we have no idea which function will be called first; the compiler might arrange to call f() first even though its value won't be needed until last. If we were to write an abomination like
        i = 1;
        a[i++] + a[i++] * a[i++]
we would have no way of knowing which order the three increments would happen in, and in fact the compiler wouldn't have any idea either. We could not argue that since multiplication has higher precedence than addition, and since multiplication associates from left to right, the second i++ would have to happen first, then the third, then the first. (Actually, associativity never says anything about which side of a single binary operator gets evaluated first; associativity says which of several adjacent same-precedence operators happens first.)
In general, you should be wary of ever trying to second-guess the relative order in which the various parts of an expression will be evaluated, with two exceptions:
  1. You can obviously assume that precedence will dictate the order in which binary operators are applied. This typically says more than just what order things happens in, but also what the expression actually means. (In other words, the precedence of * over + says more than that the multiplication ``happens first'' in 1 + 2 * 3; it says that the answer is 7, not 9.)

  2. You can assume that the && and || operators are evaluated left-to-right, and that the right-hand side is not evaluated at all if the left-hand side determines the outcome.

To look at one more example, it might seem that the code
        int i = 7;
        printf("%d\n", i++ * i++);
would have to print 56, because no matter which order the increments happen in, 7x8 is 8x7 is 56. But ++ just says that the increment happens later, not that it happens immediately, so this code could print 49 (if it chose to perform the multiplication first, and both increments later). And, it turns out that ambiguous expressions like this are such a bad idea that the ANSI C Standard does not require compilers to do anything reasonable with them at all, such that the above code might end up printing 42, or 8923409342, or 0, or crashing your computer.
Finally, note that parentheses don't dictate overall evaluation order any more than precedence does. Parentheses override precedence and say which operands go with which operators, and they therefore affect the overall meaning of an expression, but they don't say anything about the order of subexpressions or side effects. We could not ``fix'' the evaluation order of any of the expressions we've been discussing by adding parentheses. If we wrote
        f() + (g() * h())
we still wouldn't know whether f(), g(), or h() would be called first. (The parentheses would force the multiplication to happen before the addition, but precedence already would have forced that, anyway.) If we wrote
        (i++) * (i++)
the parentheses wouldn't force the increments to happen before the multiplication or in any well-defined order; this parenthesized version would be just as undefined as i++ * i++ was.
page 53
Deep sentence:
Function calls, nested assignment statements, and increment and decrement operators cause ``side effects''--some variable is changed as a by-product of the evaluation of an expression.
(There's a slight inaccuracy in this sentence: any assignment expression counts as a side effect.)
It's these ``side effects'' that you want to keep in mind when you're making sure that your programs are well-defined and don't suffer any of the undefined behavior we've been discussing. (When we informally said that complex expressions had several things going on ``at once,'' we were actually referring to expressions with multiple side effects.) As a general rule, you should make sure that each expression only has one side effect, or if it has several, that different variables are changed by the several side effects.
page 54
Deep sentence:
The moral is that writing code that depends on order of evaluation is a bad programming practice in any language. Naturally, it is necessary to know what things to avoid, but if you don't know how they are done on various machines, you won't be tempted to take advantage of a particular implementation.
The first edition of K&R said
...if you don't know how they are done on various machines, that innocence may help to protect you.
I actually prefer the first edition wording. Many textbooks encourage you to write small programs to find out how your compiler implements some of these ambiguous expressions, but it's just one step from writing a small program to find out, to writing a real program which makes use of what you've just learned. And you don't want to write programs that work only under one particular compiler, that take advantage of the way that compiler (but perhaps no other) happens to implement the undefined expressions. It's fine to be curious about what goes on ``under the hood,'' and many of you will be curious enough about what's going on with these ``forbidden'' expressions that you'll want to investigate them, but please keep very firmly in mind that, for real programs, the very easiest way of dealing with ambiguous, undefined expressions (which one compiler interprets one way and another interprets another way and a third crashes on) is not to write them in the first place.

section 2.11: Conditional Expressions in c language


section 2.11: Conditional Expressions

``Ternary'' is a ten-dollar word meaning ``having three operands.'' (It's analogous to the terms unary and binary, which refer to operators having one and two operands, respectively.) The conditional operator is a bit of a frill, and it's a bit obscure, so you may skip section 2.11 in the book on first reading, but please read the comments in these notes just below (under the mention of ``annoying compulsion'').
page 52
To see what the ?: operator has bought us, here is what the array-printing loop might look like without it:
        for(i = 0; i < n; i++) {
               printf("%6d", a[i]);
               if(i%10==9 || i==n-1)
                       printf("\n");
               else    printf(" ");
        }
You may be finding this compulsion to write ``compact'' or ``concise'' code using operators like ++ and += and ?: a bit annoying. There are three things to know:
  1. In complicated code, these operators allow an economy of expression which is beneficial. Mathematicians are constantly inventing new notations, in which one letter or symbol stands for a complicated expression or operation, in order to solve complicated problems without drowning in so much verbiage that it would be impossible to follow an argument or check for errors. Computer programs are large and complex, so well-chosen abbreviations can make them easier to work with, too.

  2. Some C programmers, it's true, do take the urge to write succinct or concise code to excess, and end up with cryptic, bewildering, obfuscated, impenetrable messes. (I'm not apologizing for them: I hate overly abbreviated, impossible-to-read code, too!)

  3. Since there is overly concise C code out there, it's occasionally necessary to dissect a piece of it and figure out what it does, so you need to have enough familiarity with these operators, and with some standard, idiomatic ways in which they're commonly combined, so that you won't be utterly stymied.

However, there is nothing that says that you have to write concise code yourself. Don't be lured into thinking that you're not a ``real C programmer'' until you routinely and easily write code which no one else can read. Write in a style that's comfortable to you; don't be embarrassed if your code seems ``simple.'' (Actually, the very best code seems simple, too.) With time, you'll probably come to appreciate at least some of the idioms, and to be comfortable enough with them that you may want to use a few of them yourself, after all.

section 2.10: Assignment Operators and Expressions in c language


section 2.10: Assignment Operators and Expressions

page 50
You may wonder what it means to say that ``expr<sub>1</sub> is computed only once'' since in an assignment like
        i = i + 2
we don't ``evaluate'' the i on the left hand side of the = at all, we assign to it. The distinction becomes important, however, when the left hand side (expr<sub>1</sub>) is more complicated than a simple variable. For example, we could add 2 to each of the n cells of an array a with code like
        int i = 0;
        while(i < n)
               a[i++] += 2;
If we tried to use the expanded form, we'd get
        int i = 0;
        while(i < n)
               a[i++] = a[i++] + 2;
and by trying to increment i twice within the same expression we'd get (as we'll see) undesired, unpredictable, and in fact undefined results. (Of course, a more natural form of this loop would be
        for(i = 0; i < n; i++)
               a[i] += 2;
and with the increment of i moved out of the array subscript, it wouldn't matter so much whether we used a[i] += 2 or a[i] = a[i] + 2.)
page 51
To make the point more clear, the ``complicated expression'' without using += would look like
        yyval[yypv[p3+p4] + yypv[p1+p2]] = yyval[yypv[p3+p4] + yypv[p1+p2]] + 2
(What's going on here is that the subexpression yypv[p3+p4] + yypv[p1+p2] is being used as a subscript to determine which cell of the yyval array to increment by 2.)
The sentence on p. 51 that includes the words ``the assignment statement has a value'' is a bit misleading: an assignment is really an expression in C. Like any expression, it has a value, and it can therefore participate as a subexpression in a larger expression. (If the distinction between the terms ``statement'' and ``expression'' seems vague, don't worry; we'll start talking about statements in the next chapter.)

section 2.9: Bitwise Operators in c


section 2.9: Bitwise Operators

page 48
The bitwise operators are definitely a bit (pardon the pun) more esoteric than the parts of C we've covered so far (and, indeed, than probably most of C). We won't concentrate on them, but they do come up all the time, so you should eventually learn enough about them to recognize what they do, even if you don't use them in any of your own programs for a while. You may skip this section for now, though.
To see what the bitwise operators are doing, it may help to convert to binary for a moment and look at what's happening to the individual bits. In the example on page 48, suppose that n is 052525, which is 21845 decimal, or 101010101010101 binary. Then n & 0177, in base 2 and base 8 (binary and octal) looks like
          101010101010101     052525
        & 000000001111111   & 000177
          ---------------     ------
                  1010101        125
In the second example, if SET_ON is 012 and x is 0, then x | SET_ON looks like
                000000000     000000
              | 000001010   | 000012
                ---------     ------
                     1010         12
and if x starts out as 402, it looks like
                100000010     000402
              | 000001010   | 000012
                ---------     ------
                100001010        412
Note that with &, anywhere we have a 0 we turn bits off, and anywhere we have a 1 we copy bits through from the other side. With |, anywhere we have a 1 we turn bits on, and anywhere we have a 0 we leave bits alone.
You'll frequently see the word mask used, both as a noun and a verb. You can imagine that we've cut a mask or stencil out of cardboard, and are using spray paint to spray through the mask onto some other piece of paper. For |, the holes in the mask are like 1's, and the spray paint is like 1's, and we paint more 1's onto the underlying paper. (If there was already paint under a hole, nothing really changes if we get more paint on it; it's still a ``1''.)
The & operator is a bit harder to fit into this analogy: you can either imagine that the holes in the mask are 1's and you're spraying some preservative which will fix some of the underlying bits after which the others will get washed off, or you can imagine that the holes in the mask are 0's, and you're spraying some erasing paint or some background color which obliterates anything (i.e. any 1's, any foreground color) it reaches.
For a bit more information on ``bitwise'' operations, see the handout, ``A Brief Refresher on Some Math Often Used in Computing.''
page 49
Work through the example at the top of the page, and convince yourself that 1 & 2 is 0 and that 1 && 2 is 1.
The precedence of the bitwise operators is not what you might expect, and explicit parentheses are often needed, as noted in this deep sentence from page 52:
Note that the precedence of the bitwise operators &, ^, and | falls below == and !=. This implies that bit-testing expressions like
  if ((x & MASK) == 0) ...
must be fully parenthesized to give proper results.