Increment WTF




  • I accidentally discovered this little number while doing something else. Is incrementing a number really so difficult?


    [IMG]http://i.imgur.com/sNob4kK.png[/IMG]


    [url="http://i.imgur.com/hzAAyR3.png"]Apparently so[/url]. I mean, I could understand if the first came out all zeros and the second came out all fives, or if the first came out 4 3 2 1 0 and the second came out 5 4 3 2 1, but the fact that they can't even consistently screw up [i]really[/i] makes me say WTF.


  • Everything about C and C++ falls in the category "Wait wait, it's not what you think; I can explain."

    You're probably better off rolling your special dice, Marquise.



  • [url=https://en.wikipedia.org/wiki/Sequence_point]Undefined behavior is undefined.[/url]

     

    [url=http://cboard.cprogramming.com/c-programming/152495-post-pre-increment.html#post1135387]Read this as well[/url].



  • @Tessellated Cheese said:

    Undefined behavior is undefined.

     @wikip said:

    in the expression f()+g() it is possible that either f() or g() will be executed first.

    I don't understand why you'd build a language this way.

     



  • @dhromed said:

    @Tessellated Cheese said:

    Undefined behavior is undefined.

     @wikip said:

    in the expression f()+g() it is possible that either f() or g() will be executed first.

    I don't understand why you'd build a language this way.

    It's to help you shoot yourself in the foot. Other languages hold your hand to help you aim, C simply places guns and feet everywhere for you to use.

    Really, it's for optimization.



  • @Tessellated Cheese said:

    Undefined behavior is undefined.

    Read this as well.



    Ah, interesting. Learn something new everyday, huh? :)



  • Any code relying on any specific behavior in this case would be bad and wrong. It would break all sorts of programming rules. You are relying on side effects of the functions.

    Specifying a specific behavior would also apply to a large amount of sane, properly written code, and would unnecessarily prevent a compiler from doing many different and effective optimizations.

    In a word, "It is wrong, don't do it." If you do, it is 'nasal demons' time.

    It is like the other example, i = i++ , which means "return i, then increment it'. It is a stupid expression - it is either a no-op, if a copy of i is made, or equivalent to just 'i++' if no copy is made- but something similar might be clever. (The designer's principle was to facilitate programmes creativity, not to put arbitrary limits on it.) Most other languages might decide to make assigning the output of i++ to anything illegal as a special rule. The C family applies the principle of allowing things, even if it creates corner cases like this that are ambiguous, in the belief that we are all adults, and can deal with it.



  • TRWTF is that neither C nor C++ has the concepts of a "keyboard" or a "screen".


  • Discourse touched me in a no-no place

    @dhromed said:

    I don't understand why you'd build a language this way.
    C is a language dominated (at the specification level) by people who think that correctness is for sissies and that making it go fast is more important. (Of course, this is nearly totally crapped on by the bone-headed implementations of some features that they've done, but that's a story for another time.)

    C++ is dominated by people who think that simplicity is for sissies and that the ideal thing is to go faster than C. With objects. And templates. And operators. And exceptions.

    Later languages are much more cautious about the specified evaluation order; it inhibits some optimizations, but lets behavior be much more predictable. (Both Java and C# are like this, as are most scripting languages.)



  • @Tessellated Cheese said:

    Undefined behavior is undefined.
    @Wackypedia said:
    An often-cited example is the C expression i=i++, which apparently both assigns i its previous value and increments i.
    No, it does not "assign i its previous value".  The current value of i is incremented and that new value is assigned to i.  Whoever wrote that is retarded @Wackypedia said:
    The final value of i is ambiguous, because, depending on the order of expression evaluation, the increment may occur before, after, or interleaved with the assignment.  In C and C++, evaluating such an expression yields undefined behavior.
    i=i++ is the equivalent of what was being done in BASIC (and possibly other languages) 30 years ago:  a = a+1, which was understood to be a logical and unambigous way of incrementing something.  Funny how everyone understood this perfectly 30 years ago but now it's "undefined".  TRWTF is C/C++.  TRWTF #2 is using two plus signs to increment something by one.

     



  • @henke37 said:

    TRWTF is that neither C nor C++ has the concepts of a "keyboard" or a "screen".
    Care to elaborate?



  • Oh you've discovered the joy known as "the difference between pre- and post- increment!" There's a pistol in the corner. It's loaded with one bullet. You know what to do.



  • @El_Heffe said:

    i=i++ is the equivalent of what was being done in BASIC (and possibly other languages) 30 years ago:  a = a+1, which was understood to be a logical and unambigous way of incrementing something

    No. The statement i++; is equivalent to i = i + 1;. It includes the assignment. Its value as an expression is the value of i before the increment. i = i++ should be (but isn't) an error, since the extra assignment is at best redundant, or at worst the complete opposite of the intended behavior. Many compilers will at least warn about this, at least when the highest warning level is active. If = was a sequence point, i = i++ would be a no-op. But it's not, so the compiler is free to (and will) reorder the sub-operations, making this undefined behavior.



  • @dhromed said:

    @Tessellated Cheese said:

    Undefined behavior is undefined.

     @wikip said:

    in the expression f()+g() it is possible that either f() or g() will be executed first.

    I don't understand why you'd build a language this way.

    Some (common) calling conventions push arguments onto the stack from right to left (I assume so that the arguments appear in the "right" order in memory or something?). Evaluating the arguments from left to right then pushing them to the stack from right to left would be rather slow and complicated.

    And no, you can't "just change the default calling convention" unless you want to break back-compat with every bit of compiled code for your system



  • @El_Heffe said:

    i=i++ is the equivalent of what was being done in BASIC (and possibly other languages) 30 years ago:  a = a+1, which was understood to be a logical and unambigous way of incrementing something

    No. Equivalents of BASIC's a = a+1 in C are a = a+1; or a += 1; or a++; or ++a;

    BASIC doesn't have equivalents for C's increment and decrement side effect operators, which were added to the language to capture the semantics of the PDP-11 processor's auto-increment and auto-decrement addressing modes, as in while (*dst++ = *src++); to copy a zero-terminated string (this compiles to two PDP-11 instructions, a move and a conditional branch).

    a = a++ is a bizarre construction specifying two assignments to a, one of them due to the = operator and involving the value of a before the post-increment operator ++ alters it, and the other due to the post-increment operator itself. The order in which these will occur is not defined. Don't do that.



  • @El_Heffe said:

    TRWTF #2 is using two plus signs to increment something by one.
    While I like the conciseness of ++i, namely inside of for loops, I'd be cool with the complete elimination of the pre-increment operator in favour of i += 1 or even i = i + 1.

    I have mixed feelings about i++. I generally avoid it, as there seems to be little reason to use it: if you want to first use the current value of i and then increment it, then you should be explicit and do it in two separate statements, right? It makes sense that each statement has a single purpose. And yet, when it's an offset you know is going to be accessed sequentially it just seems so natural to post-increment on the spot.



  • @Zecc said:

    I'd be cool with the complete elimination of the pre-increment operator in favour of i += 1 or even i = i + 1.

    C, unlike BASIC, allows you to assign stuff to a dereferenced address e.g.

    *expensive_function_returning_pointer(parameters, possibly, with, their, own, side, effects) += 6;
    and if the compound assignment operators didn't exist you'd need to clutter the logic with temporary variables to deal with this case.



  • @blakeyrat said:

    Oh you've discovered the joy known as "the difference between pre- and post- increment!"

    Similar confusion could just as easily be obtained by using either pre- or post-increment in this antipattern. The issue is that applying multiple side effects to a single variable between successive sequence points has undefined results.


  • Discourse touched me in a no-no place

    @El_Heffe said:

    i=i++ is the equivalent of what was being done in BASIC (and possibly other languages) 30 years ago:  a = a+1, which was understood to be a logical and unambigous way of incrementing something.
    Not quite. ++i is the equivalent of that. i++ increments the value and returns the value before incrementing. In some order. (I believe that C has both because of one of the weirder addressing modes on the early PDP-11.) i=i++ assigns the old value and increments in arbitrary order (which is then optimized to either an increment or a no-op, arbitrarily of course). This is all thoroughly mad.

    C++ lets you override the behavior of increment yet keeps the arbitrariness



  • @flabdablet said:

    @blakeyrat said:
    Oh you've discovered the joy known as "the difference between pre- and post- increment!"

    Similar confusion could just as easily be obtained by using either pre- or post-increment in this antipattern. The issue is that applying multiple side effects to a single variable between successive sequence points has undefined results.

    I think the lesson here is that C++ is a bad language that is bad and you should use something less bad.


  • Java Dev

    @dkf said:

    @El_Heffe said:
    i=i++ is the equivalent of what was being done in BASIC (and possibly other languages) 30 years ago:  a = a+1, which was understood to be a logical and unambigous way of incrementing something.
    Not quite. ++i is the equivalent of that. i++ increments the value and returns the value before incrementing. In some order. (I believe that C has both because of one of the weirder addressing modes on the early PDP-11.) i=i++ assigns the old value and increments in arbitrary order (which is then optimized to either an increment or a no-op, arbitrarily of course). This is all thoroughly mad.

    C++ lets you override the behavior of increment yet keeps the arbitrariness

     

    It's not necessarily one of those two - it could do anything.

    If you write i=++i you might tend to say it assigns the same value to i either way making it unambiguous, however it's still undefined behaviour.

     



  • @blakeyrat said:

    I think the lesson here is that C++ is a bad language that is bad and you should use something less bad.

    Every language has its quirks.



  • @electronerd said:

    @blakeyrat said:
    I think the lesson here is that C++ is a bad language that is bad and you should use something less bad.

    Every language has its quirks.

    Ok first of all, that talk has been linked here about 4578,2313 times so give it a rest already.

    Secondly, of course every language has its quirks. Duh. Some languages like PHP have 80,000 quirks and some languages like C# have 10 quirks. Use the languages with fewer quirks.

    One of the reasons our industry never fucking progresses anywhere is that people are still fighting shitty languages and doing manual memory management when they COULD be out there writing great products.



  • @dkf said:

    one of the weirder addressing modes on the early PDP-11

    The PDP-11's neat (and, IIRC, patented) trick was having the instruction set coded so as to treat the program counter as just another general purpose register. This allows immediate, absolute, PC-relative and PC-relative-indirect address modes to be encoded as special cases of general register-related modes where the register concerned happens to be R7, the program counter. No processor before or since has encoded its address modes so neatly.

    You could even apply pre-decrement indirect addressing to the program counter:

    MOV -(R7), -(R7)
    is a backward self-replicator.



  • @blakeyrat said:

    One of the reasons our industry never fucking progresses anywhere is that people are still fighting shitty languages and doing manual memory management when they COULD be out there writing great products.

    Another is hinted at by this famous quote from Charles Babbage:

    On two occasions I have been asked, — "Pray, Mr. Babbage, if you put into the machine wrong figures, will the right answers come out?" In one case a member of the Upper, and in the other a member of the Lower, House put this question. I am not able rightly to apprehend the kind of confusion of ideas that could provoke such a question.

    It seems to me that the inability to apprehend that kind of confusion is exactly what separates those who can program from those who can't, and that this is why no universally usable application has ever been written.



  • @Vempele said:

    Really, it's for optimization.

    Unfortunately, most compilers lack an important option: un-optimize. Basically it should treat all undefined behavior in the most unintuitive and software-breaking way so you can test if your program is affected or not.


    Oh, and it could also warn you when it sees things like cout << a++ << a++;. That would be nice too.


  • @Zecc said:

    While I like the conciseness of ++i, namely inside of for loops, I'd be cool with the complete elimination of the pre-increment operator in favour of i += 1 or even i = i + 1.
    Assignops are TRWTF.  If i += 1 means the same thing as i = i + 1, then shouldn't i != 1 mean the same as i = i ! 1?

    And i == 1 the same as i = i = 1?



  •  There was time, when add-assign (and other such operators) could be written as

    i =+ 1;

    Not kidding.



  • @flabdablet said:

    The PDP-11's neat (and, IIRC, patented) trick was having the instruction set coded so as to treat the program counter as just another general purpose register. This allows immediate, absolute, PC-relative and PC-relative-indirect address modes to be encoded as special cases of general register-related modes where the register concerned happens to be R7, the program counter. No processor before or since has encoded its address modes so neatly.

    You could even apply pre-decrement indirect addressing to the program counter:

    MOV -(R7), -(R7)
    is a backward self-replicator.

    We all know [url=http://infocenter.arm.com/help/topic/com.arm.doc.dui0473c/Babbdajb.html]there's no widely-used processor today which allows the program counter to be accessed like a general-purpose register.[/url].



  • @da Doctah said:

    @Zecc said:

    While I like the conciseness of ++i, namely inside of for loops, I'd be cool with the complete elimination of the pre-increment operator in favour of i += 1 or even i = i + 1.
    Assignops are TRWTF.  If i += 1 means the same thing as i = i + 1, then shouldn't i != 1 mean the same as i = i ! 1?

    And i == 1 the same as i = i = 1?

    If i >= 1 means the same thing as i > 1 || i == 1, shouldn't i >> 1 mean the same thing as i > 1 || i >= 1?



  • @dkf said:

    C is a language dominated (at the specification level) by people who think that correctness is for sissies and that making it go fast is more important.

    Or even more fundamentally, that languages should be designed to make compilers as simple as possible, even if it makes the language harder to use. (See also: Unix.)

    @dkf said:

    C++ is dominated by people who think that simplicity is for sissies and that the ideal thing is to go faster than C. With objects. And templates. And operators. And exceptions.

    C++ is dominated by misery vampires who feed off the suffering of others.



  • @flabdablet said:

    BASIC doesn't have equivalents for C's increment and decrement side effect operators, which were added to the language to capture the semantics of the PDP-11 processor's auto-increment and auto-decrement addressing modes, as in while (*dst++ = *src++); to copy a zero-terminated string (this compiles to two PDP-11 instructions, a move and a conditional branch).

    I'm too lazy to find a cite, but I'm pretty sure this is wrong. The operators pre-date the PDP-11; the fact that its addressing modes make the string copy a matter of two instructions was a happy coincidence.



  • @blakeyrat said:

    One of the reasons our industry never fucking progresses anywhere is that people are still fighting shitty languages and doing manual memory management when they COULD be out there writing great products.

    This is a bit like assuming the mental ward patients could invent a cure for cancer if they'd just stop eating their own shit.



  • @dkf said:

    @dhromed said:
    I don't understand why you'd build a language this way.
    C is a language dominated (at the specification level) by people who think that correctness is for sissies and that making it go fast is more important. (Of course, this is nearly totally crapped on by the bone-headed implementations of some features that they've done, but that's a story for another time.)

    C++ is dominated by people who think that simplicity is for sissies and that the ideal thing is to go faster than C. With objects. And templates. And operators. And exceptions.

    Or, more briefly: C sucks. C++ sucks++.

     



  • @morbiuswilters said:

    @blakeyrat said:
    One of the reasons our industry never fucking progresses anywhere is that people are still fighting shitty languages and doing manual memory management when they COULD be out there writing great products.

    This is a bit like assuming the mental ward patients could invent a cure for cancer if they'd just stop eating their own shit.

    Well maybe the premise is wrong.

    But it would be nice if people *tried*.



  • @morbiuswilters said:

    The operators pre-date the PDP-11; the fact that its addressing modes make the string copy a matter of two instructions was a happy coincidence.

    Really? First appearance of those operators I'm aware of is in B, which I understand was first implemented on a PDP-11.



  • @morbiuswilters said:

    C++ is dominated by misery vampires who feed off the suffering of others.

    You're not the only one who thinks so.



  • @flabdablet said:

    @morbiuswilters said:
    The operators pre-date the PDP-11; the fact that its addressing modes make the string copy a matter of two instructions was a happy coincidence.

    Really? First appearance of those operators I'm aware of is in B, which I understand was first implemented on a PDP-11.

    You're right about the operators coming from B, but I believe B was first implemented on the PDP-7..

    Bell Labs' C history. The part on ++ / -- is the second paragraph under the "More History" heading:

    @Dennis Ritchie said:

    Thompson went a step further by inventing the ++ and -- operators, which increment or decrement; their prefix or postfix position determines whether the alteration occurs before or after noting the value of the operand. They were not in the earliest versions of B, but appeared along the way. 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. Indeed, the auto-increment cells were not used directly in implementation of the operators, and a stronger motivation for the innovation was probably his observation that the translation of ++x was smaller than that of x=x+1.



  • @morbiuswilters said:

    Bell Labs' C history.

    Ah! Thanks for that.



  • @Zecc said:

    @henke37 said:

    TRWTF is that neither C nor C++ has the concepts of a "keyboard" or a "screen".
    Care to elaborate?

    The C and C++ standard libraries have those concepts!
    @D-Coder said:

    @dkf said:

    @dhromed said:
    I don't understand why you'd build a language this way.
    C is a language dominated (at the specification level) by people who think that correctness is for sissies and that making it go fast is more important. (Of course, this is nearly totally crapped on by the bone-headed implementations of some features that they've done, but that's a story for another time.)

    C++ is dominated by people who think that simplicity is for sissies and that the ideal thing is to go faster than C. With objects. And templates. And operators. And exceptions.

    Or, more briefly: C sucks. C++ sucks++.

     

    C++11 has many changes and improvements that make the language more usable and more flexible, and they have even fixed some mistakes where they could.



  • @da Doctah said:

    @Zecc said:

    While I like the conciseness of ++i, namely inside of for loops, I'd be cool with the complete elimination of the pre-increment operator in favour of i += 1 or even i = i + 1.
    Assignops are TRWTF.  If i += 1 means the same thing as i = i + 1, then shouldn't i != 1 mean the same as i = i ! 1?

    And i == 1 the same as i = i = 1?

    Nice troll, i lol'd


  • Discourse touched me in a no-no place

    @PleegWat said:

    It's not necessarily one of those two - it could do anything.
    I hate that phrase. I'd complain if it did anything other than assigning the unincremented value or that value plus one. Starting WWIII “just because” would be considered to be a significant compiler bug.

    Later languages have more precise semantics where this problem goes away.



  • @esoterik said:

    C++11 has many changes and improvements that make the language more usable and more flexible, and they have even fixed some mistakes where they could.

    Short of deleting every scrap of C++ source code in existence and putting a bullet through Stroustrup's fat, dumb head, what do you think could possibly "fix" C++? I just looked at the Wikipedia page for C++11 and it seems like a bunch of clumsy workarounds for problems that shouldn't have existed in a well-thought-out language in the first place and some more "kitchen sink" features, because the only way Stroustrup can get off at night is knowing that his language contains every single feature that has ever existed in any other language.



  • @morbiuswilters said:

    @dkf said:
    C is a language dominated (at the specification level) by people who think that correctness is for sissies and that making it go fast is more important.
    Or even more fundamentally, that languages should be designed to make compilers as simple as possible, even if it makes the language harder to use. (See also: Unix.)
    Not that I want to jump in defending that position, but the more complex a piece of software is, the greater the potential for it having bugs.

    So, from that perspective, keeping a compiler as simple as possible is a desireable goal, because you want to be able to trust it to not mangle the code you send through it - a compiler bug that leads to it creating incorrect machine code for source code that is actually formally correct has the potential to cause a lot of problems in the long run (more or less, depending on how subtle the introduced "wrongness" is, and how long it takes someone to spot it).



  • @Tessellated Cheese said:

    Undefined behavior is undefined.
     

    Care to elaborate? The article you linked seems to suggest that there is a sequence point between each increment.

     



  • @Anonymouse said:

    Not that I want to jump in defending that position, but the more complex a piece of software is, the greater the potential for it having bugs.

    Yes, but when you shove all that complexity into the application itself, you just make lots of buggy apps. Making the compiler a little more complex to eliminate complexity (and hence bugs) in application code is the right choice, and it's what more modern languages (and even some that predate C, sadly..) have done. It's a lot easier to shake out bugs from a compiler and have everybody benefit, rather than have each application confront the same complex requirements imposed by a poorly-thought-out (but simple to implement language!)

    tl;dr: Worse is better.


  • Discourse touched me in a no-no place

    @spamcourt said:

    Unfortunately, most compilers lack an important option: un-optimize. Basically it should treat all undefined behavior in the most unintuitive and software-breaking way so you can test if your program is affected or not.
    c.f. the DeathStation9000 C compiler, one of the favoured ones on comp.lang.c.



  • @Zecc said:

    I have mixed feelings about i++.
    I have mixed feelings about C++. No, actually, that's not true. I hate it with a passion.

     



  • @Faxmachinen said:

    @Tessellated Cheese said:
    Undefined behavior is undefined.
    Care to elaborate? The article you linked seems to suggest that there is a sequence point between each increment.

    Replace the stream insertion operator stream << item with an equivalent function ins(stream, item) and you will see that the expression

    cout << a << b << c << d << e
    is equivalent to
    ins(ins(ins(ins(ins(cout, a), b), c), d), e)

    The order of evaluation for function parameters is compiler-dependent. So although each call to ins() constitutes a sequence point, and you can be sure that both cout and a get completely evaluated before being passed to the innermost ins(), you can't be sure that the innermost ins() is itself called before evaluation of b and so on, which means you can't be sure of the order in which a, b c, d and e are evaluated.

    Thor's results suggest that the compiler he's using evaluates function parameters right to left.



  • @Thor said:


    I accidentally discovered this little number while doing something else. Is incrementing a number really so difficult?





    Apparently so. I mean, I could understand if the first came out all zeros and the second came out all fives, or if the first came out 4 3 2 1 0 and the second came out 5 4 3 2 1, but the fact that they can't even consistently screw up really makes me say WTF.
     

    Wow, is this really undefined in the C++ spec?  I'd have thought it'd be defined as doing exactly what MSVC++ is doing for those two cases.


Log in to reply