3

I'm working through K&R and am presently on Exercise 1-16. It occurs to me that thus far only pre-increment has been used in the book.

Most other tutorial books and indeed source code I've seen tend to favour post-increment, except where there is an obvious affect such as in while loops etc.

Is this a stylistic or technical consideration on the part of K&R? Or do I just need to be further through the book to get my answer?!

retrodev
  • 2,323
  • 6
  • 24
  • 48
  • It's probably also related to the PDP-11 instruction set. – Paul R Jul 02 '13 at 21:34
  • 1
    Not quite an exact duplicate, but highly relevant: http://stackoverflow.com/questions/24886/is-there-a-performance-difference-between-i-and-i-in-c – Adam Rosenfield Jul 02 '13 at 21:36
  • 1
    PDP-11 had post-increment and pre-decrement. Convenient for stack operations. – lurker Jul 02 '13 at 21:42
  • @PaulR "It's probably also related to the PDP-11 instruction set. " -- Dennie Ritchie has explicitly said that it isn't ... these operations in C and its predecessors predated the PDP-11. – Jim Balter Jul 02 '13 at 22:03
  • @C.Lang "Well technically pre-in/decrement takes less steps" -- This is simply false. – Jim Balter Jul 02 '13 at 22:09
  • @C.Lang First, there's no difference if the value isn't being assigned ... if it is, then pre and post can't be interchanged. Second, you comments don't reflect understanding of the compiler's target, or code generation generally. Without registers: `mov num2, num1 / inc num2`; with registers: `mov num2,r / mov r,num1 / inc r / mov r,num2` -- There is no basis whatsover for the "Well technically" comment ... it shows a lack of knowledge of the relevant technology. – Jim Balter Jul 02 '13 at 23:44
  • Here is the actual "technical" point that those making rash, unexamined claims are getting confused over: If `p` is used after the loop or if there's no flow analysis, `while ((c = *p++)) { ... }` requires that `p` be incremented whether the test succeeds or fails. – Jim Balter Jul 02 '13 at 23:54
  • It's not a tangent, it's not pointless, and I made no baseless assumptions ... these are unwarranted and unsubstantiated claims. – Jim Balter Jul 03 '13 at 19:15
  • I retract my original statement "...takes less steps...". I did what I should've done in the first place and looked at a disassembly. @JimBalter is correct in all cases *including* the assignment statements. Meaning the generated code is the same and indeed takes the same number of "steps". Deleting all related comments. – ChiefTwoPencils Jul 03 '13 at 20:36

3 Answers3

9

About 10 million years ago, before the dawn of optimizing compilers, this used to matter*. It really doesn't anymore. The rationale was that an additional store operation is required in order to implement post-increment in the naive manner. Compilers mostly don't implement it this way though, so just prefer whichever style makes more sense given the context and trust your compiler to do the right thing.

*JimBalter correctly pointed out that this was never a problem, and I should not have suggested that it once might have mattered.

Gian
  • 13,735
  • 44
  • 51
  • 7
    This still matters with C++ and iterators, where `operator++` and `operator--` are overloaded with more complex operations. For simple types like `int`, however, it doesn't matter. – Jens Jul 02 '13 at 21:35
  • I had a hard time accepting that after reading in the Qt documentation that it *did* matter and should always use pre when it doesn't matter. Of course I've since been unable to locate it again. – ChiefTwoPencils Jul 02 '13 at 21:36
  • True, though the question was specifically about K&R :) – Gian Jul 02 '13 at 21:46
  • Citation required for *"About 10 million years ago, before the dawn of optimizing compilers"* – paddy Jul 02 '13 at 21:53
  • 1
    I was picking a conservative lower bound on the period before good-quality optimizing compilers. – Gian Jul 02 '13 at 21:58
  • It was never an issue for any compiler ... this is a myth. – Jim Balter Jul 02 '13 at 22:10
  • 1
    For *any* compiler? I remember writing a really terrible C compiler for class years ago that would almost certainly have been afflicted by this. – Gian Jul 02 '13 at 22:14
  • @Gian Your anecdote about a memory of writing extremely bad software isn't relevant. It isn't an *issue* for any compiler. No additional store operation is required ... that is simply a false statement. The only place that there is a code generation issue is when the post increment is part of the controlling condition of a loop. – Jim Balter Jul 02 '13 at 23:49
  • I'm confused as to which point you're making. We're emphatically agreeing that it is not an issue, I think, and that it would be a very silly compiler indeed in which this might make any difference at all to the code that is generated, right? – Gian Jul 02 '13 at 23:58
  • @Gian: This is not about being an "issue" for a compiler and what code it may or may not produce. The difference between pre/post for basic types is none. The difference between pre/post for iterators `it` is an extra saved state: whereas `++it` increments and uses the result directly, `it++` needs to keep a temporary value of `it` around for use in surrounding expression, and then increment after use. Depending on how the iterator itself is implemented, that can make a costly difference. – Jens Jul 03 '13 at 11:17
  • @Jens: so would you say that with *non*-basic types that there's an extra "step"? Speaking both rashly and "technically" of course. I can't seem to make sense of Jim's point. To me your first comment here is a better said version of my first in the question; no? – ChiefTwoPencils Jul 03 '13 at 19:04
  • "I can't seem to make sense of Jim's point." -- That certainly isn't *my* problem. There is no extra step. Perhaps if people would bother to explicitly state the generated code, they would see that. The only case where post-increment is possibly less efficient is when it is in the controlling condition of a loop -- the non-incremented value is tested but the incremented value must be available both inside and outside the loop after the test is made, unless an optimizer can avoid that when the post-incremented value isn't used outside the loop. – Jim Balter Jul 03 '13 at 19:21
  • @Gian "We're emphatically agreeing that it is not an issue" -- No, C. Lang thinks it is, or once was, an issue. "it would be a very silly compiler indeed in which this might make any difference at all to the code that is generated, right?" -- You're the one who asserted that you had written such a silly compiler when I noted that it was never an issue for any compiler ... that was a pointless and irrelevant comment. There is no *reason* for any compiler to be "afflicted by this". – Jim Balter Jul 03 '13 at 19:27
  • 1
    @C.Lang: Correct. The "extra step" would be storing a temporary value. There are some interesting discussions on "C++ and pre/post increment" around here. But best to write a test and look at the disassembly yourself :) – Jens Jul 03 '13 at 19:30
  • @JimBalter, I was simply providing a counter-example to your overly-strong quantification. – Gian Jul 03 '13 at 20:42
  • 1
    It's worth pointing out that @JimBalter is actually correct though. I erred in suggesting that it ever might have been a problem outside of the specific case (i.e., loop condition) where it might be an issue. Thanks, JimBalter! – Gian Jul 03 '13 at 20:49
  • @Jens No, C.Lang is not correct; there is no store of a temporary value ... see http://softwareramblings.com/2010/01/post-increment-vs-pre-increment.html – Jim Balter Jul 03 '13 at 21:20
  • 1
    Here's another case where postincrement requires an extra store (and is why it matters for iterators): `int foo() { static int x; return x++;}` -- since `x` must be incremented but its pre-increment value must be accessed *after* it is incremented, then a temp is needed. But this is *not* the sort of case the OP asked about, and it's not a stylistic issue ... `++x` and `x++` are semantically different. When they *aren't* semantically different, no temp is needed. And that's the last I'm going to say in this long, silly, and ignorance-driven thread. – Jim Balter Jul 03 '13 at 21:31
  • Just compiling `++x` and `x++` to instructions by hand was sufficient to convince me of this. I recommend anyone else do the same if they remain unconvinced. – Gian Jul 04 '13 at 04:40
5

There are several aspects to this.

Semantics

The semantics of pre-increment (decrement) and post-increment (decrement) is different. Whereas the former increments a value before the value is used, the latter increments a value after its use. For example

unsigned i;
for (i=0; i<10; i++ /* ++i */ ) { } 

In this case it would not matter whether one uses pre- or post-increment. Which brings me to the second aspect:

Context

Sometimes it does matter whether to use pre- or post-increment, and that depends on the context of the use. Consider the following example:

char *p = "some string";
unsigned len = 0;

// test 1
while ('\0' != *p++) { len++; }

// test 2 (assumes that p points to a non-empty string)
while ('\0' != *++p) { ++len; }

The result of test 1 is the length of the string, the result of test 2 is the length of the string minus one. Because the incremented value p is used as part of an expression, it does matter when the increment happens: after the use of p in the expression (test 1), or before its use (test 2). Because len is not used as part of an expression, it does not matter whether one uses pre- or post-increment.

And that brings me to the third aspect.

Implementation

In order to implement a post-increment, the value of p must be stored away for its later use to increment it, which takes up extra storage space for a temporary value. Pre-increment does not require temporary storage space. Modern optimizing compilers, however, are able to generate the same code for pre- and post-increment, if the incremented value is not used as part of an expression where the temporary value would be needed to evaluate that expression.

Which brings me to the fourth aspect (somewhat hinted at in the previous paragraph), but that is more C++ related.

Performance

In C++ the decrement and increment operators can be overloaded (link) to work with complex types, and STL (link) does make substantial use of that to implement iterators (link) for its container types. In that case, a value like

set::iterator it;

can be a rather complex thing with more than trivial state (i.e. it's not just a primitive integer). In that case, using pre-increment ++it does make a difference over post-increment it++ because there is no need to store a temporary value away like it is needed for post-increment if used in an expression (see Context). And that can save quite some runtime overhead.

Jens
  • 8,423
  • 9
  • 58
  • 78
  • Excellent answer, thank you. As an aside, why do you compare `null` to `*p++` and not the other way around? I.e. `while ('\0' != *p++)` not `while (*p++ != '\0')`. The latter makes more immediate sense to my mind. – retrodev Jul 03 '13 at 21:26
  • 1
    @retrodev: It's a bit of a habit of mine to avoid accidental assignments. If I write `if (x == 5)` then a typo like `if (x = 5)` results in an always-true assignment. Whereas if I write `if (5 == x)` then that same typo would issue a compiler error because I can not assign values `x` to integrals `5`. – Jens Jul 03 '13 at 21:36
  • The section on implementation is (somewhat) wrong. If a temp is needed, then an optimizer can't eliminate it. Whether it is needed is not a matter of whether or not it's a primitive type, but whether the pre-incremented value must be accessed after the increment occurs ... that can be true of primitive types. It is virtually always true of iterators because the iterator has global state, and that global state must be updated within the iterator code, before the return value (the pre-incremented state) is used. – Jim Balter Jul 03 '13 at 21:38
  • @JimBalter: Correct, and that's exactly what I say in the section "Context". – Jens Jul 03 '13 at 21:42
  • No, that obviously is *not* exactly what you say, and even if it were it wouldn't make the errors I pointed out in the implementation section go away. But I'm done with this subject. – Jim Balter Jul 03 '13 at 21:46
  • @JimBalter: Made my statement more precise of what I intended to say. – Jens Jul 03 '13 at 21:57
  • 2
    Note that test 2 (`while ('\0' != *++p) { ++len; }`) fails horribly if `p` points to an empty string (so `*p == '\0'` immediately before loop entry) because it skips over the null and pokes around in undefined territory. – Jonathan Leffler Jul 11 '15 at 04:47
  • @JonathanLeffler: yes, thank you, I've modified the answer. – Jens Jul 11 '15 at 09:47
1

I would say (even if post-increment operator comes first in the Standard because of higher precedence of postfix operators) that pre-increment is the natural way of incrementing the value of an object so that's a good reason to use it if the only thing you want is to increment the value of an object,

(++E) is equivalent to (E+=1) but not (E++)

ouah
  • 142,963
  • 15
  • 272
  • 331