[This article was originally posted on January 18, 1999; I have touched it up lightly since then.]
From: scs@eskimo.com (Steve Summit)
Newsgroups: comp.lang.c
Subject: Re: a++ versus ++a
Date: 18 Jan 1999 16:31:45 GMT
Lines: 457
Message-ID: <77vnlh$8qq$1@eskinews.eskimo.com>
References: <36A2B0E3.95E2A918@newsandmail.com>
In article <36A2B0E3.95E2A918@newsandmail.com>,
dan.johnson@newsandmail.com writes:
> Here's my understanding Incrimenting and Pre-Incrimenting....
> a++ means the same as a + 1
> ++a means the same as 1 + a
Well, no. That wouldn't be much of a difference, would it?
> What I don't understand is what the benefit of one versus the other
> is inside of code. I have used a++ many times in my for-loops
> [ie, for (a = 0; a > 10; a++) ... etc]
Part of the trick of understanding the difference between a++ and ++a is to realize that sometimes, there is no difference. The difference between a++ and ++a is in what value is "returned" to the surrounding expression, but this obviously makes a difference only when there is a surrounding expression, when the a++ or ++a appears as a subexpression within a larger expression. When the a++ or ++a stands alone, as it does in the loop header
for(a = 0; a < 10; a++)
there is no effective difference at all; that loop is 100% equivalent to one that begins
for(a = 0; a < 10; ++a)
When the a++ or ++a does appear as a subexpression within a larger expression, the difference is that a++ "returns" the old, unincremented value of a to the surrounding expression, while for ++a you get the new, incremented value.
> I learn best by simple example, so if you have some code you can show
> off the best use and explanation of the differences between these, then
> by all means supply it in a reply.
Here's some code. (The example is going to seem excessively long, but underlying that length is the simplicity you asked for, so please bear with me. It's long because I'm going to try to make it realistic; I'm going to motivate the choice between prefix and postfix ++ with a convincing example, rather than an arbitrary fragment like b = a++, and I'm going to provide a fair amount of commentary along the way.)
Suppose we have an array of integers
int numbers[10];
which we're going to fill in with numbers read from the user. We're not sure how many numbers the user is going to type in -- sometimes it will be less than 10 -- so we'll keep a second variable,
int nnumbers;
to keep track of how many numbers the user did type in. Our very first cut at writing the code to read in the numbers might look something like this:
int i, x; for(i = 0; i < 10; i++) { x = getnumberfromuser(); if(x <= 0) break; numbers[i] = x; } nnumbers = i;
Here we've deferred the question of how exactly we're going to accept numbers from the user by moving that functionality off into a function, getnumberfromuser(). (I'll provide a sample implementation below, for completeness.) We've hypothesized that getnumberfromuser() will return 0 or a negative number to indicate that the user has finished typing in numbers (perhaps because we'll simply ask the user to type in 0 or -1 to indicate the end of the list). Since the numbers[] array is size 10, we start off imagining that we'll make 10 trips through the loop to fill in up to 10 numbers. If the user indicates the end of the list (that is, types 0 or -1) after typing fewer than 10 numbers, we won't be getting 10 numbers, after all, so we take an early exit from the loop with the break statement.
The code above will work, but it has a few deficiencies. The most serious (though the least obvious) is that if the getnumberfromuser() function explicitly prompts the user to enter a number (which is quite a likely implementation), the user experience will be significantly different depending on whether the user types 10 numbers, or fewer than 10 numbers. If the user types, say, 7 numbers, another prompt will appear (which would be for the 8th number), and the user will type 0 or -1 to indicate completion. If the user types 10 numbers, however, the loop will take its normal exit (because the condition i < 10 in the for loop will no longer be true), and getnumberfromuser() will not be called at all, and no 11th prompt will appear, and the user won't have to type (or be given the opportunity to type) 0 or -1 at all.
Other deficiencies (though they're more minor stylistic quibbles) concern the loop used and the number of variables used. The loop
for(i = 0; i < 10; i++)
suggests that we expect the loop to be traversed 10 times most of the time, except under exceptional circumstances, but in fact if it's normal for the user to type fewer than 10 numbers, the loop will exit abnormally (via the break statement) most of the time. (And if the user does frequently type all 10 numbers, it's possible that the constant 10 in the program is too small, that the user would like to be typing more numbers, if given the chance.) There's nothing wrong with using a break statement, especially when the condition it represents is truly exceptional, but this particular use isn't the best.
Finally, there's at least one more variable in this first code fragment than we really need. Within the loop, the variable i keeps track of how many trips we've made through the loop and hence how many numbers the user has typed so far. But at the end of the loop, we copy i out into our other variable, nnumbers, which we had specified would contain the final number of numbers the user eventually typed. It turns out we can get by with just i or just nnumbers; we don't need both. (There's nothing intrinsically wrong with using more variables than are strictly required -- an overzealous attempt to re-use variables can lead to buggy or difficult-to-understand programs -- but sometimes, as here, extra variables indicate that the flow of the code is not as clean as it could be.)
A better loop would emphasize that we're going to accept numbers as long as the user keeps volunteering them, and would relegate the i < 10 test to the exceptional condition, since that test is more of an accident of the (rather arbitrary) choice of 10 as the maximum number of numbers the program can support. Writing a loop that accepts numbers as long as the user keeps typing them sounds much more like a while loop than a for loop, and indeed the kind of loop we want to write, in pseudo-code, will look something like
while(there's another number) { store it in numbers[]; increment nnumbers; }
One way to implement the loop "while(there's another number)" would be
while(getnumberfromuser() > 0)
But that won't work for this example, because when a number came back from the getnumberfromuser() function, we'd (properly) compare it with zero to determine whether it was a positive number or a zero-or-negative sentinel indicating that the user was finished, but then we'd lose track of the just-returned value and wouldn't be able to store it in the numbers[] array, which was supposed to be our primary job. What we really want to do, of course, is save the returned value away in a variable so that we can both test it against 0 and (if necessary) use it to fill in one of the elements of the array. The obvious way to do that (which is about what we used in our first attempt) is
x = getnumberfromuser(); if(x > 0) store x in numbers[] and continue reading; else we're done reading numbers;
The slightly less-than-obvious way, but which is ideal for while loops like the one we're trying to write, is to bury the assignment inside the conditional:
(x = getnumberfromuser()) > 0
Here we store getnumberfromuser's return value into the variable x, and then use the "return" value of the assignment operator as the left-hand operand of the > operator. The "return" value of an assignment operator is simply the value assigned, so the net result is that we both store getnumberfromuser's return value away in x for future reference, and also immediately compare it against 0 to see if it's a number we want, or not. Placed into a while loop (but still using some pseudocode), the expression is used like this:
while((x = getnumberfromuser()) > 0) { store x in numbers[]; } we're done reading numbers;
If you don't mind the embedded assignment-and-test epitomized by the expression (x = getnumberfromuser()) > 0, this is a splendid way to write the loop, and it corrects the first stylistic deficiency. (Actually, I hope you don't mind the embedded assignment-and-test, because it's quite a popular idiom in C!) Now I'll move on to the second deficiency, because even though it was quite minor, it's going to be at the center of our decision whether to use prefix or postfix ++ (which is, believe it or not, still what I'm ultimately trying to demonstrate here).
I want to remove the extra variable involved in the first example. When we're done reading numbers, the variable nnumbers is supposed to contain the number of numbers the user has entered. I'm going to assert that, within the loop, nnumbers is always going to contain the number of numbers the user has entered so far. (The notion that "nnumbers always contains the number of numbers the user has entered so far" is an example of a "loop invariant", and picking clean, simple loop invariants and sticking to them is an excellent technique when we want to write clean, readable code.)
Let's think about the values that nnumbers should contain as the program fragment proceeds. Before we make any trips through the loop, the user won't have typed any numbers, so nnumbers should (rather obviously) start out as 0. At the end of the first trip through the loop (assuming the user doesn't type 0 or -1 right away), nnumbers should contain 1. At the end of the second complete trip through the loop, nnumbers should contain 2, etc.
If we're going to stick with using the nnumbers variable, and eliminate i as redundant, we're going to have to use nnumbers to decide which element of the numbers[] array to store each new number in. Since arrays in C are 0-based, the first number read will be stored in numbers[0], the second number will be stored in numbers[1], etc. So it looks like we can store each new number into numbers[nnumbers], as long as we're careful to increment nnumbers after we use it to decide which element of the array to store in. Our code (which is no longer pseudocode) therefore looks like this:
nnumbers = 0; while((x = getnumberfromuser()) > 0) { numbers[nnumbers] = x; nnumbers = nnumbers + 1; }
We've met our goals of abandoning the for loop in favor of the (for this problem) more-natural while loop, and of consolidating our use of the former variable i into the nnumbers variable. Our loop invariant is (mostly) met: at the end of each trip through the loop, after incrementing nnumbers, nnumbers does indeed contain the number of numbers read so far. (We've also lost some functionality, and introduced a bug: we're no longer checking to make sure we store no more than 10 numbers in the array, so it could overflow. We'll fix this up in a minute.)
Our purpose here (I still haven't forgotten) is to demonstrate the choice between prefix and postfix ++. If we want to replace the "longhand" expression nnumbers = nnumbers + 1 with the shorthand ++ form, we could (I suppose) write the body of the loop as
{ numbers[nnumbers] = x; nnumbers++; }
but this doesn't buy us much (nor is it an effective demonstration of the prefix vs. postfix decision). Here, the expression nnumbers++ stands alone, with no surrounding expression, so it doesn't matter whether it "returns" the old or the new value. We could just as well have written
{ numbers[nnumbers] = x; ++nnumbers; }
We can, however, collapse the two statements in the loop together, with a result that will look like (reverting to pseudocode again for a moment):
numbers[use nnumbers and also increment it] = x;
There are two ways to look at this process of collapsing: we can say that it's just another example of C programmers trying to show off and be obfuscated by cramming everything possible into one line, or we can say that it's a good way of expressing an idiom and strengthening a loop invariant. (The tack this article is taking will seem to favor the latter interpretation, but I'm not going to try to jam it down your throat; I concede that the C programmer proclivity to cram everything into one line can be annoying even when there are good reasons for it.)
How do we decide whether to replace the pseudoexpression "use nnumbers and also increment it" with ++nnumbers or nnumbers++? Remember, we wanted to store the first value read into numbers[0], or stated another way, to use nnumbers as a subscript before incrementing it. Prefix ++ means to increment the variable and "return" the new incremented value, while postfix ++ yields the old, unincremented value. So postfix ++ is exactly what we want here; the old body of the loop collapses down to the single expression
numbers[nnumbers++] = x;
When we put everything together, we'll also take the opportunity to restore the missing overflow check:
nnumbers = 0; while((x = getnumberfromuser()) > 0) { if(nnumbers >= 10) { printf("too many numbers!\n"); break; } numbers[nnumbers++] = x; }
The nice thing about this code, besides the fact that it restores the missing overflow check, is that its loop invariant -- "nnumbers always contains the number of numbers the user has entered so far" -- is even stronger. When we wrote
numbers[nnumbers] = x; nnumbers = nnumbers + 1;
there was one spot in the code, namely in the middle of the loop between the two statements, where the invariant wasn't true, after all. The reason for collapsing those two lines down to a single expression is not to show off how much we can accomplish in a single expression, it's because the tasks of "use nnumbers to decide which element of the array to use" and "increment nnumbers" are (or should be) very tightly linked. It would be a serious error to perform one task but not the other. By expressing the two tasks within one expression in one statement, we reinforce the important connection between them. (I should point out, though, that this is still only a stylistic consideration. There will be little if any difference in performance between the longhand and shorthand forms of the loop, and if we were worried about more esoteric issues such as the truth of the loop invariant in the face of interrupts, there would be no significant difference between them in that respect, either.)
If you trace back through the discussion so far, you'll find that our choice of postfix ++ was rather closely tied to the fact that arrays are 0-based in C. Let's suppose, for a moment, that for some reason you wanted to use a 1-based array for numbers[]. In other words, you want the first value read to go into numbers[1], the second into numbers[2], etc. If you don't mind wasting one element per array, it's simple enough to simulate a 1-based array in C -- just declare the array one bigger than it has to be:
int numbers[11];
Now you can use numbers[1] through numbers[10], and just kind of pretend that numbers[0] isn't there.
In languages that support 1-based arrays by default, it's unfortunately rather common to see code which (to mimic our ongoing example) initializes nnumbers to 1, since that's the first subscript value that will be used. In other words, one way (though a poor way) to write the loop would be
nnumbers = 1; while((x = getnumberfromuser()) > 0) { numbers[nnumbers] = x; nnumbers = nnumbers + 1; } nnumbers = nnumbers - 1; /* correct for going one too far */
There's an obvious problem with this code: if its loop invariant was supposed to be "nnumbers always contains the number of numbers the user has entered so far", it's now false most of the time; it's true only in the middle of the loop, between the two statements. In fact, a more accurate loop invariant for the code as written here would be that "nnumbers always contains one more than the number of numbers the user has entered so far", which is obviously a much less useful statement. A poor loop invariant like this always has to be propped up with fudge factors here and there, indicated by apologetic comments such as "correct for going one too far".
But 1-based arrays don't have to lead to convoluted code. Look how much simpler the code becomes if we interchange the increment and the array assignment, and change the initial value of nnumbers back to 0:
nnumbers = 0; while((x = getnumberfromuser()) > 0) { nnumbers = nnumbers + 1; numbers[nnumbers] = x; }
Now, it's again mostly true that "nnumbers always contains the number of numbers the user has entered so far", but since we increment nnumbers before using it, the first number read goes into numbers[1].
You can probably see where this is going. Replacing the longhand form numbers = numbers + 1 with the autoincrement operator ++, and restoring the overflow test, we end up with this 1-based version:
nnumbers = 0; while((x = getnumberfromuser()) > 0) { if(nnumbers >= 10) { printf("too many numbers!\n"); break; } numbers[++nnumbers] = x; }
If you compare this code to the previous 0-based version, you'll see that the only difference is that it uses prefix ++ instead of postfix.
To summarize, then: the difference between prefix and postfix ++ is that prefix "returns" to the surrounding expression the new, incremented value of the variable, while postfix yields the old, unincremented value. This distinction makes no difference if the prefix or postfix expression stands alone as a full expression; it only makes a difference when the prefix or postfix expression sits as a subexpression within a larger expression which cares about the value "returned." It may seem alarming that it's taken me 457 lines to describe this distinction, and it's true that for most of the issues I've elaborated on here so agonizingly, experienced C programmers make their decisions without thinking about each tiny detail so acutely. But if you're trying to decide whether to use prefix or postfix autoincrement in some code, code which you've gotten as far as the pseudocode
numbers[use nnumbers and also increment it] = x;
in defining, you do need to think about the fact that arrays are 0-based in C, and about your loop invariant, before making your decision. When you do, you'll find that the prefix and postfix forms are not interchangeable, that the distinction between them is significant and real, and that (for any given situation in which the value is significant) one of them does just what you want while the other would be quite wrong.
Steve Summit
P.S. Here's the promised implementation of getnumberfromuser(), for completeness:
#include <stdio.h> int getnumberfromuser() { char tmpbuf[25]; printf("enter a number [-1 to exit]: "); fflush(stdout); if(fgets(tmpbuf, sizeof(tmpbuf), stdin) == NULL) return -1; return atoi(tmpbuf); }