Disclaimer: I know perfectly well the semantics of prefix and postfix increment. So please don't explain to me how they work.

Reading questions on stack overflow, I cannot help but notice that programmers get confused by the postfix increment operator over and over and over again. From this the following question arises: is there any use case where postfix increment provides a real benefit in terms of code quality?

Let me clarify my question with an example. Here is a super-terse implementation of strcpy:

while (*dst++ = *src++);

But that's not exactly the most self-documenting code in my book (and it produces two annoying warnings on sane compilers). So what's wrong with the following alternative?

while (*dst = *src)
{
++src;
++dst;
}

We can then get rid of the confusing assignment in the condition and get completely warning-free code:

while (*src != '\0')
{
*dst = *src;
++src;
++dst;
}
*dst = '\0';

(Yes I know, src and dst will have different ending values in these alternative solutions, but since strcpy immediately returns after the loop, it does not matter in this case.)

It seems the purpose of postfix increment is to make code as terse as possible. I simply fail to see how this is something we should strive for. If this was originally about performance, is it still relevant today?

This question came from our site for professional and enthusiast programmers.

6

The postfix variant is the most commonly used one, so if you're going to make a case against it, it better be a pretty strong case. And your first example is a bit of a straw man, since I suspect few people would actually code the strcpy method this way (for reasons you have already mentioned).
–
Robert HarveyFeb 1 '11 at 0:01

If we removed everything from the language that had the potential to confuse programmers, we wouldn't have very many features. The fact something isn't useful to you, or only very rarely useful, doesn't mean it should be snipped. If it's not relevant anymore, don't use it; the end.
–
Cody GrayFeb 1 '11 at 0:01

4

Yeah but then you can never do int c = 0; c++;
–
Biff MaGriffFeb 1 '11 at 0:06

4

@Cody: Because some things are both confusing and useful. He isn't confused by post-increment itself, he's confused about it's usefulness. We shouldn't have things that are useless, confusing or not.
–
GManNickGFeb 1 '11 at 0:34

10 Answers
10

While it did once have some performance implications, I think the real reason is for expressing your intent cleanly. The real question is whether something while (*d++=*s++); expresses intent clearly or not. IMO, it does, and I find the alternatives you offer less clear -- but that may (easily) be a result of having spent decades becoming accustomed to how things are done. Having learned C from K&R (because there were almost no other books on C at the time) probably helps too.

To an extent, it's true that terseness was valued to a much greater degree in older code. Personally, I think this was largely a good thing -- understanding a few lines of code is usually fairly trivial; what's difficult is understanding large chunks of code. Tests and studies have shown repeatedly, that fitting all the code on screen at once is a major factor in understanding the code. As screens expand, this seems to remain true, so keeping code (reasonably) terse remains valuable.

Of course it's possible to go overboard, but I don't think this is. Specifically, I think it's going overboard when understanding a single line of code becomes extremely difficult or time consuming -- specifically, when understanding fewer lines of code consumes more effort than understanding more lines. That's frequent in Lisp and APL, but doesn't seem (at least to me) to be the case here.

I'm less concerned about compiler warnings -- it's my experience that many compilers emit utterly ridiculous warnings on a fairly regular basis. While I certainly think people should understand their code (and any warnings it might produce), decent code that happens to trigger a warning in some compiler is not necessarily wrong. Admittedly, beginners don't always know what they can safely ignore, but we don't stay beginners forever, and don't need to code like we are either.

+1 for pointing out that the terse version is easier to read to some of us. :-)
–
R..Feb 1 '11 at 0:56

1

If you don't like some of your compiler warnings (and there are more than a few I could do without - seriously, I meant to type if(x = y), I swear!) you should adjust your compiler's warnings options so you don't accidentally miss the warnings you do like.
–
Chris LutzFeb 1 '11 at 2:09

4

@Brooks Moses: I'm a lot less enthused about that. I don't like polluting my code to make up for the compiler's shortcomings.
–
Jerry CoffinFeb 1 '11 at 2:23

1

@Jerry, @Chris: This annotation is intended for the case where you think the warning is usually a good thing, but you don't want it to apply to a particular line that's doing something unusual. If you never want the warning and consider it a shortcoming, use -Wno-X, but if you only want to make one exception to it and want the warning to apply everywhere else, this is much more comprehensible than using arcane hoop-jumping such as Chris refers to.
–
Brooks MosesFeb 1 '11 at 2:50

1

@Brooks: the double parentheses and the (void)x to silence unused warnings are fairly well known idioms to silence otherwise useful warnings. The annotation looks a bit like pragmas, not portable at best :/
–
Matthieu M.Feb 1 '11 at 7:28

It is, err, was, a hardware thing

Interesting that you would notice. Postfix increment is probably there for a number of perfectly good reasons, but like many things in C, it's popularity can be traced to the origin of the language.

Although C was developed on a variety of early and underpowered machines, C and Unix first hit the relative big-time with the memory-managed models of the PDP-11. These were relatively important computers in their day and Unix was by far better -- exponentially better -- than the other 7 crummy operating systems available for the -11.

And, it so happens, that on the PDP-11,

*--p

and

*p++

...were implemented in hardware as addressing modes. (Also *p, but no other combination.) On those early machines, all less than 0.001 GHz, saving an instruction or two in a loop must almost have been a wait-a-second or wait-a-minute or go-out-for-lunch difference. This doesn't precisely speak to postincrement specifically, but a loop with pointer postincrement could have been a lot better than indexing back then.

As a consequence, the design patterns because C idioms which became C mental macros.

It's something like declaring variables right after a { ... not since C89 was current has this been a requirement, but it's now a code pattern.

Update: Obviously, the main reason *p++ is in the language is because it is exactly what one so often wants to do. The popularity of the code pattern was reinforced by popular hardware which came along and matched the already-existing pattern of a language designed slightly before the arrival of the PDP-11.

These days it makes no difference which pattern you use, or if you use indexing, and we usually program at a higher level anyway, but it must have mattered a lot on those 0.001GHz machines, and using anything other than *--x or *x++ would have meant you didn't "get" the PDP-11, and you would might have people coming up to you and saying "did you know that..." :-) :-)

Wikipedia says: "A (false) folk myth is that the instruction set architecture of the PDP-11 influenced the idiomatic use of the C programming language. The PDP-11's increment and decrement addressing modes correspond to the −−i and i++ constructs in C. If i and j were both register variables, an expression such as *(−−i) = *(j++) could be compiled to a single machine instruction. [...] Dennis Ritchie unambiguously contradicts this folk myth. [The Development of the C Language]"
–
fredoverflowFeb 1 '11 at 0:20

3

Huh (From the link provided in FredOverflow's comment): "People often guess that they were created to use the auto-increment and auto-decrement address modes provided by the DEC PDP-11 on which C and Unix first became popular. This is historically impossible, since there was no PDP-11 when B was developed. The PDP-7, however, did have a few `auto-increment' memory cells, with the property that an indirect memory reference through them incremented the cell. This feature probably suggested such operators to Thompson; the generalization to make them both prefix and postfix was his own."
–
Kit ScuzzFeb 1 '11 at 0:28

3

DMR only said that the PDP-11 was not the reason it was added to C. I said that too. What I said was that it was used to advantage on the PDP-11 and became a popular design pattern for that exact reason. If Wikipedia contradicts that then they are simply wrong, but I don't read it that way. It is phrased poorly on that W page.
–
DigitalRossFeb 1 '11 at 0:30

3

@FredOverflow. I think you misunderstood exactly what the "folk myth" is. The myth is that C was designed to exploit those 11 ops, not that they don't exist and not that the code pattern didn't become popular because everyone knew they mapped to the 11 well. In case it isn't clear: the PDP-11 really really does implement those two modes in hardware for almost every instruction as an addressing mode, and it really was the first important machine C ran on.
–
DigitalRossFeb 1 '11 at 0:32

2

"it must have mattered a lot on those 0.001GHz machines". Um, I hate to say it because it dates me, but I had Motorola and Intel manuals for my CPUs to look up the size of the instructions and how many cycles they took. Finding the smallest codes added up to being able to add one more feature to a print spooler and saving a couple cycles meant a serial port could keep up with some protocol like that newly invented MIDI. C relieved some of the nit-picking, especially as the compilers improved, but still it was important knowing what was the most efficient way to write code.
–
the Tin ManFeb 1 '11 at 6:57

The obvious reason for the postincrement operator to exist is so that you don't have to write expressions like (++x,x-1) or (x+=1,x-1) all over the place or uselessly separate trivial statements with easy-to-understand side effects out into multiple statements. Here are some examples:

while (*s) x+=*s++-'0';
if (x) *s++='.';

Whenever reading or writing a string in the forward direction, postincrement, and not preincrement, is almost always the natural operation. I've almost never encountered real-world uses for preincrement, and the only major use I've found for predecrement is converting numbers to strings (because human writing systems write numbers backwards).

Edit: Actually it wouldn't be quite so ugly; instead of (++x,x-1) you could of course use ++x-1 or (x+=1)-1. Still x++ is more readable.

You have to be careful about those expressions since you are modifying and reading a variable between two sequence points. The order in which those expressions are evaluated is not guaranteed.
–
SnowmanAug 7 '14 at 21:36

@Snowman: Which one? I see no such issues in my answer.
–
R..Aug 7 '14 at 21:42

if you have something like (++x,x-1) (function call, maybe?)
–
SnowmanAug 7 '14 at 21:46

@Snowman: That's not a function call. It's the C comma operator, which is a sequence point, parenthesized to control precedence. The result is the same as x++ in most cases (one exception: x is an unsigned type with rank lower than int and the initial value of x is the max value of that type).
–
R..Aug 7 '14 at 22:02

Ah, the tricky comma operator... when a comma is not a comma. The parenthesis and rarity of the comma operator threw me off. Nevermind!
–
SnowmanAug 7 '14 at 22:03

I doubt it was ever really necessary. As far as I know, it doesn't compile into anything more compact on most platforms than using the pre-increment as you did, in the loop. It was just that at the time it was made, terseness of code was more important than clarity of code.

That isn't to say that clarity of code isn't (or wasn't) important, but when you were typing in on a low baud modem (anything where you measure in baud is slow) where every keystroke had to make it to the mainframe and then get echoed back one byte at a time with a single bit used as parity check, you didn't want to have to type much.

This is sort of like the & and | operator having lower precedence than ==

It was designed with the best intentions (a non-short circuiting version of && and ||), but now it confuses programmers everyday, and it probably won't ever change.

Anyway, this is only an answer in that I think there is not a good answer to your question, but I'll probably be proven wrong by a more guru coder than I.

--EDIT--

I should note that I find having both incredibly useful, I'm just pointing out that it won't change whether anyone like it or not.

This isn't even remotely true. At no point was "terseness of code" more important than clarity.
–
Cody GrayFeb 1 '11 at 0:10

Well, I would argue that it was much more of a point of focus. You're quite right though, it's certainly never been more important than clarity of code... to most people.
–
Kit ScuzzFeb 1 '11 at 0:13

6

Well, the definition of "terse" is "smoothly elegant: polished" or "using few words: devoid of superfluity," both of which are generally a good thing when writing code. "Terse" doesn't mean "obscure, hard to read, and obfuscated" like many people think.
–
James McNellisFeb 1 '11 at 0:21

@James: While you're correct, I think most people mean the second definition of "terse" when using it in a programming context: "using few words; devoid of superfluity". Under that definition, it's certainly easier to imagine situations where there is a trade-off between the brevity and expressiveness of a particular piece of code. Obviously the right thing to do when writing code is the same thing as when speaking: make things as short as they need to be, but no shorter. Valuing terseness over expressivity can lead to obfuscated-looking code, but it certainly shouldn't.
–
Cody GrayFeb 1 '11 at 0:41

1

rewind 40 years and imagine you had to fit your program on that drum that will hold only around 1000 characters, or write a compiler can only work with 4 thousand bytes of ram. There were times where terseness vs clarity was not even an issue. You had to be terse, there didn't exist a computer on this planet that could store, run or compile your non-terse program.
–
nosFeb 5 '11 at 1:38

I like the postfix aperator when dealing with pointers (whether or not I'm dereferencing them) because

p++

reads more naturally as "move to the next spot" than the equivalent

p += 1

which surely must confuse beginners the first time they see it used with a pointer where sizeof(*p) != 1.

Are you sure you're stating the confusion problem correctly? Is not the thing that confuses beginners the fact that the postfix ++ operator has higher precedence than the dereference * operator so that

Amongst the subtle elements of good programming are localisation and minimalism:

putting variables in a minimal scope of use

using const when write access isn't required

etc.

In the same spirit, x++ can be seen as a way of localising a reference to the current value of x while immediately indicating that you've - at least for now - finished with that value and want to move to the next (valid whether x is an int or a pointer or iterator). With a little imagination, you could comparable this to letting the old value/position of x go "out of scope" immediately after it's no longer needed, and moving to the new value. The precise point where that transition is possible is highlighted by the postfix ++. The implication that this is probably the final use of the "old" x can be valuable insight into the algorithm, assisting the programmer as they scan through the surrounding code. Of course, the postfix ++ may put the programmer on the lookout for uses of the new value, which may or may not be good depending on when that new value is actually needed, so it's an aspect of the "art" or "craft" of programming to determine what's more helpful in the circumstances.

While many beginners may be confused by a feature, it's worth balancing that against the long-term benefits: beginners don't stay beginners for long.

Wow, lots of answers not-quite-on-point (if I may be so bold), and please forgive me if I'm pointing out the obvious – particularly in light of your comment to not point out the semantics, but the obvious (from Stroustrup's perspective, I suppose) doesn't yet seem to have been posted! :)

Postfix x++ produces a temporary that's passed 'upwards' in the expression, while the the variable x is subsequently incremented.

Prefix ++x does not produce a temporary object, but increments 'x' and passes the result to the expression.

Of course, the results of this example can be produced from other equivalent (and perhaps less oblique) code.

So why do we have the postfix operator? I guess because it's an idiom that's persisted, in spite of the confusion it obviously creates. It's a legacy construct that once was perceived to have value, though I'm not sure that perceived value was so much for performance as for convenience. That convenience hasn't been lost, but I think an appreciation of readability has increased, resulting in the questioning of the operator's purpose.

Do we need the postfix operator anymore? Likely not. Its result is confusing and creates a barrier to understandability. Some good coders will certainly immediately know where to find it useful, often in places where it has a peculiarly "PERL-esque" beauty. The cost of that beauty is readability.

I agree that explicit code has benefits over terseness, in readability, understandability and maintainability. Good designers and managers want that.

However, there's a certain beauty that some programmers recognize that encourages them to produce code with things purely for beautiful simplicity – such as the postfix operator – which code they wouldn't have otherwise ever have thought of – or found intellectually stimulating. There's a certain something to it that just makes it more beautiful, if not more desirable, in spite of pithiness.

In other words, some people find while (*dst++ = *src++); to simply be a more beautiful solution, something that takes your breath away with its simplicity, just as much as if it were a brush to canvas. That you are forced to understand the language to appreciate the beauty only adds to its magnificence.

+1 "some people find while (*dst++ = *src++); to simply be a more beautiful solution". Definitely. People who don't understand assembly language don't really understand how elegant C can be, but that particular code statement is a shining star.
–
the Tin ManFeb 1 '11 at 6:45

"Do we need the postfix operator anymore? Likely not." - I can't help thinking of Turing machines here. Have we ever needed automatic variables, the for statement, heck, even while (we have goto, after all)?
–
Bernd JendrissekFeb 1 '11 at 19:37

Closures! How ridiculous. Just give me a tape! Seriously, what I meant is that I don't think /needing/ a feature should be the primary decision criterion (unless you want to design, say, lisp). IME I use (nay, need) the postfix operators almost exclusively, and only rarely need the prefix one instead.
–
Bernd JendrissekFeb 2 '11 at 15:14

I'm going to disagree with the premise that ++p, p++, is somehow difficult to read or unclear. One means "increment p and then read p", the other means "read p and then increment p". In both cases, the operator in the code is exactly where it is in the explanation of the code, so if you know what ++ means, you know what the resulting code means.

You can create obfuscated code using any syntax, but I don't see a case here that p++/++p is inherently obfuscatory.

For some reason, the post-increment operator looks very appealing. Its short, its sweet, and it increases everything by 1. Compare it with prefix:

while( ++i )

"looks backwards" doesn't it? You never see a plus sign FIRST in math, except when someone's being silly with specifying something's positive (+0 or something like that). We're used to seeing OBJECT + SOMETHING

Is it something to do with grading? A, A+, A++? I can tell you I was pretty cheesed at first that the Python people removed operator++ from their language!

Your examples don't do the same thing. i++ returns the original value of i, and i+=1 and ++i return the original value of i plus 1. Since you're using the return values for control flow, this matters.
–
David ThornleyDec 28 '11 at 15:23