Live data from Hacker News

Int a = 5; a = a++ + ++a; a =? (2011)

gynvael.coldwind.pl

41–50 of 246 posts

Re: Int a = 5; a = a++ + ++a; a =? (2011)

#41

Earlier quoted context omitted.

It's valuable for compilers to be able to choose the instruction scheduling order. Standards authors try not to unnecessarily bind implementors. If post increment happened after the full statement is finished, then the original value has to be maintained until the next sequence point. Maybe the compiler will be smart enough to elide that, maybe not, but it's a lot more difficult to fix those kinds of edge cases than…

But this is not valuable if doing so results in different numerical results, and I think that will always happen if ++ is executed at different times, there's no point in a compiler optimizing pointless code that can silently give different results elsewhere

Your compiler does many optimizations that break numerical reproducibility, especially in floats. I reviewed a PR the other day that wrote X=AB+(CD)+E;

And when I checked 3 different compilers, each of them chose a different way to use FMAs.

Even with integer math, you can get different numerical results via UB (e.g. expressions with signed overflow one way and not another).

Re: Int a = 5; a = a++ + ++a; a =? (2011)

#42
I had to fight through school and university in India with my teachers who believed these were legit questions to ask in written exams. Can't 100% blame them since almost all standard-issue textbooks had them and claimed they'd give predictable output. I thought the same until I noticed the weirdness when running them across different compilers and after I read about UB, sequence points and similar quirks in books that are not total garbage.

Luckily, I ended up with smug smiles in all those cases after showing them the output from different compilers.

Re: Int a = 5; a = a++ + ++a; a =? (2011)

#43
post #7

What's the reason that C didn't define the order of this? The horrible undefined behavior of signed integer overflow at least can be explained by the fact that multiple CPU architectures handling those differently existed (though the fact that C even 'attracts' its ill-defined signed integers when you're using unsigned ones by returning a signed int when left shifting an uint16_t by an uint16_t for example is not as…

Applying the increment or decrement operators over the same variable more than once on the same line should be a compile-time error. Anyway, yes, this one example has an obvious order it should be applied. But still, something like it shouldn't be allowed.

> Applying the increment or decrement operators over the same variable more than once on the same line should be a compile-time error

That would be nice, but don't forget the more general case of pointers and aliasing:

    int a = 5;
    int *pa = &a;
    printf("%d", (a++ + ++*pa));
The compiler cannot statically catch every possible instance of a statement where a variable is updated more than once.

Re: Int a = 5; a = a++ + ++a; a =? (2011)

#44
post #20

Earlier quoted context omitted.

Applying the increment or decrement operators over the same variable more than once on the same line should be a compile-time error. Anyway, yes, this one example has an obvious order it should be applied. But still, something like it shouldn't be allowed.

Honestly, having increment in expressions rather than a statement feels like more of a footgun than a benefit. Expressions shouldn't mutate things.

I think the history of this is that these operations were common with assembly programmers, so when C came along, these were included in the language to allow these developers to feel they weren't leaving lots of performance behind.

Look at the addressing modes for the PDP-11 in https://en.wikipedia.org/wiki/PDP-11_architecture and you'll see you can write (R0)+ to read the contents of the location pointed to by R0, and then increment R0 afterwards (so a post increment).

Back in the day, compilers were simple and optimisations weren't that common, so folding two statements into one and working out that there were no dependencies would have been tough with single pass compilers.

You could argue that without such instructions, C wouldn't have been embraced quite so enthusiastically for systems programming, and the world would have looked rather different.

Re: Int a = 5; a = a++ + ++a; a =? (2011)

#45
post #20

Earlier quoted context omitted.

Applying the increment or decrement operators over the same variable more than once on the same line should be a compile-time error. Anyway, yes, this one example has an obvious order it should be applied. But still, something like it shouldn't be allowed.

Honestly, having increment in expressions rather than a statement feels like more of a footgun than a benefit. Expressions shouldn't mutate things.

Python recently went the other way and added an assignment expression. I actually wish more languages would go further and add statement expressions instead of having to imitate them with IIFEs.

C just wouldn't be C without things like a[i++]

Re: Int a = 5; a = a++ + ++a; a =? (2011)

#46
post #43

Earlier quoted context omitted.

Applying the increment or decrement operators over the same variable more than once on the same line should be a compile-time error. Anyway, yes, this one example has an obvious order it should be applied. But still, something like it shouldn't be allowed.

> Applying the increment or decrement operators over the same variable more than once on the same line should be a compile-time error That would be nice, but don't forget the more general case of pointers and aliasing: int a = 5; int *pa = &a; printf("%d", (a++ + ++*pa)); The compiler cannot statically catch every possible instance of a statement where a variable is updated more than once.

Well, aliased updates are undefined behavior already.

Re: Int a = 5; a = a++ + ++a; a =? (2011)

#47
post #38
post #20

Earlier quoted context omitted.

Honestly, having increment in expressions rather than a statement feels like more of a footgun than a benefit. Expressions shouldn't mutate things.

In any language where the practice of iteration isn't achieved via C-style for-loops, having an operator devoted to increment just doesn't make sense (let alone four operators, for each of pre/post-increment/decrement). This is one of those backwards things that just needs to be chucked in the bin for any language developed post-2010.

When used well it makes for compact readable code. I don't see what it has to do with for loops or operators specifically. For example you can do the same in scheme while iterating by means of tail recursion.

Re: Int a = 5; a = a++ + ++a; a =? (2011)

#48
post #7

What's the reason that C didn't define the order of this? The horrible undefined behavior of signed integer overflow at least can be explained by the fact that multiple CPU architectures handling those differently existed (though the fact that C even 'attracts' its ill-defined signed integers when you're using unsigned ones by returning a signed int when left shifting an uint16_t by an uint16_t for example is not as…

The short answer is because C was designed to give leeway to really dumb compilers on really diverse hardware.

This isn't quite the same case, but it's a good illustration of the effect: on gcc, if you have an expression f(a(), b()), the order that a and b get evaluated is [1] dependent on the architecture and calling-convention of f. If the calling convention wants you to push arguments from right to left, then b is evaluated first; otherwise, a is evaluated first. If you evaluate arguments in the right order, then after calling the function, you can immediately push the argument on the stack; in the wrong order, the result is now a live variable that needs to be carried over another function call, which is a couple more instructions. I don't have a specific example for increment/decrement instructions, but considering extremely register-poor machines and hardware instruction support for increment/decrement addressing modes, it's not hard to imagine that there are similar cases where forcing the compiler to insert the increment at the 'wrong' point is similarly expensive.

Now, with modern compilers using cross-architecture IRs as their main avenue of optimization, the benefit from this kind of flexibility is very limited, especially since the penalties on modern architectures for the 'wrong' order of things can be reduced to nothing with a bit more cleverness. But compiler developers tend to be loath to change observable behavior, and the standards committee unwilling to mandate that compiler developers have to modify their code, so the fact that some compilers have chosen to implement it in different manners means it's going to remain that way essentially forever. If you were making a new language from scratch, you could easily mandate a particular order of evaluation, and I imagine that every new language in the past several decades has in fact done that.

[1] Or at least was 20 years ago, when I was asked to look into this. GCC may have changed since then.

Re: Int a = 5; a = a++ + ++a; a =? (2011)

#49
post #20

Earlier quoted context omitted.

Applying the increment or decrement operators over the same variable more than once on the same line should be a compile-time error. Anyway, yes, this one example has an obvious order it should be applied. But still, something like it shouldn't be allowed.

Honestly, having increment in expressions rather than a statement feels like more of a footgun than a benefit. Expressions shouldn't mutate things.

Those things are for pointer golf and writing your entire logic inside the if statement.

Both are favorite idioms of C developers. And they are ok if done correctly, clearer than the alternative. They are also unnecessary in modern languages, so those shouldn't copy it (yeah, Python specifically).

Post reply on HN