Comment by card_zero

16 hours ago

Because people choose to use pre-increment by default instead of post-increment?

Why is that?

It should be ++C because with C++ the value you get from the expression is the old one.

If you're asking why people use pre-increment by default instead of post-increment, it's mostly historical. The early C compilers on resource-constrained platforms such as early DOS were not good at optimization; on those, pre-increment would be reliably translated to a simple ADD or INC, whereas code for post-increment might generate an extra copy even if it wasn't actually used.

For C++ this was even worse with iterators, because now it depended on the compiler's ability to inline its implementation of postfix ++, and then prove that all the copies produced by that implementation have no side effects to optimize it to the same degree as prefix ++ could. Depending on the type of the underlying value, this may not even be possible in general.

The other reason is that all other unary operators in C are prefix rather than postfix, and mixing unary prefix with unary postfix in a single expression produces code that is easy to misunderstand. E.g. *p++ is *(p++), not (*p)++, even though the latter feels more natural, reading it left-to-right as usual. OTOH *++p vs ++*p is unambiguous.

  • K&R seems to use pre-increment early on, then post-increment consistently (or a lot, anyway, I haven't done a thorough check) after chapter 3, in situations where either would do. In fact, after introducing post-increment at 2.8.

  • > It should be ++C because with C++ the value you get from the expression is the old one.

    You get it!

The PDP-11 that C originally targeted had address modes to support the stack. Pre-increment and post-decrement therefore did not require a separate instruction; they were free. After the PDP-11 went the way of the dodo, both forms took a machine cycle so it (mostly) became a stylistic issue. (The two operators have different semantics, but the trend to avoid side-effects in expressions means that both are most often used in a single expression statement like "++x;" or "x++;", so it comes down to your preferred style.)

  • Please explain what you mean by "a separate instruction".

    • Some idiomatic C code to copy a string (I'm not saying this is good C code, but it's just an example):

          while(*d++ = *s++)
            ;
       

      On the Motorola 68000 (based somewhat on the PDP-11) the code would look like:

          loop:       move.b  (a0)+,d0
                      move.b  d0,(a1)+
                      bne     loop
       

      while on the x86 line, it would be:

          loop:       mov     al,[rsi]
                      mov     [rdi],al
                      inc     rsi     ; extra instruction!
                      inc     rdi     ; extra instruction!
                      cmp     al,0
                      jne     loop
       

      Yes, there are better ways to write that code for both the 68K and x86, but I hope this gets the point across.

Why would you use post increment by default? The semantics are very particular.

Only on very rare occasions I need post increment semantics.

And in those cases I prefer to use a temporary to make the intent more clear

  • I rarely use pre-increment tbh, but post-increment all the time for array indices (since typically the array should be indexed with the value before the increment happens).

    If the pre- or post-increment behaviour isn't actually needed, I prefer `x += 1` though.

  • People seem to mostly write a typical for loop ending with ; ++i){

    But I write ; i++){ and seeing it the other way round throws me off for a minute, because I think, as you put it, why would you use those very particular semantics?

    But I guess this is only a semantic argument.

    • In C++ the semantics can differ, in that copying an object for post-increment might require a memory allocation internally (for example in the case of a BigInt class), which may fail and throw an exception. For consistency, using pre-increment by default and unless you really need post-increment, is a good habit.

    • > why would you use those very particular semantics?

      The difference is that i++ has to keep a copy to the original around as the return value is the pre-increment value, while with ++i that isn't needed as the resulting value is being returned.

      In the for loop that shouldn't matter as a) for an integer it is essentially for free (it is just reordering when the relevant register is set) and b) that value is hopefully optimized out anyways by the compiler, however as there are cases where it matters some people prefer the ++i style, some just think it looks better.

    • It makes no difference if the increment is done on an int, but it can make a different if your `i` is some object with its own ++ operator.

    • I've seen either style, but it the argument about which is proper is pointless. Any modern compiler will optimize either equally well, unless you're doing something that actually depends on the order of the increment.

      1 reply →

  • If you're used to the idiom, the intent couldn't be clearer.

    I miss it when switching between C/++ and other languages.

Why use this operator? Like most C and C++ features the main reason tends to be showing off, you learned a thing (in this case that there are four extra operators here) and so you show off by using it even if it doesn't make the software easier to understand.

This is not one of those beginner -> journeyman -> expert cycles where coincidentally the way you wrote it as a beginner is identical to how an expert writes it but for a very different reason. I'd expect experts are very comfortable writing either { x = k; k += 1; } or { k += 1; x = k; } depending on which they meant and don't feel an itch to re-write these as { x = k++; } and { x = ++k; } respectively.

I'm slightly surprised none of the joke languages add equally frivolous operators. a%% to set a to the remainder after dividing a by 10, or b** to set b as two to the power b or some other silliness.

  • They can be useful when adding things to an array in a loop. A trivial example which removes a character from a null terminated string:

      void remove_char(char *s, char c) {
        size_t i, j;
    
        for (i = j = 0; s[i] != '\0'; i++)
          if (s[i] != c)
            s[j++] = c;
        s[j] = '\0';
      }
    
    

    This might be better expressed with a higher order filter function, but C is too low level for things like that.

    There are also idioms for stack manipulation using them: "stack[sp++] = pushed" and "popped = stack[--sp]".

    C code does a lot of incrementing and decrementing by one, and so having dedicated syntax for it is convenient.

    • Note that in your example there appear to be three distinct meanings:

      1. prefix incr/decr precedence: "stack[--sp]"

      2. postfix incr/decr precedence: "s[j++]"

      3. i have no particular preference for the precedence and am just using a shorthand I inherited from my ancestors whose use cases are no longer relevant to me: "i++" in your for loop

      My rank speculation is that C programmers get in a habit of #3 and then forget to consider precedence in an expression where it matters.

      In any case, it would be interesting to do a scan of github to see how often prefix and suffix incr/decr had to get switched up in a bugfix patch.