Live data from Hacker News

Weekend projects: getting silly with C

lcamtuf.substack.com

91–100 of 118 posts

Re: Weekend projects: getting silly with C

#91
post #38

This reminds me of some silly C code I once wrote for fun, which counts down from 10 to 1: #include // compile & run: gcc -Wall countdown.c -o countdown && ./countdown int n = 10; int main(int argc, char *argv[]) { printf("%d\n", n) && --n && main(n, NULL); } Python version: import sys # run: python3 countdown.py 10 def main(n:int): sys.stdout.write(f"{n}\n") and n-1 and main(n-1) main(int(sys.argv[1])) Shell version…

Nitpick: you could replace sys.stdout.write(f"{n}\n") with print(n). The current code looks very much like it was written for Python 2 (apart from the f string!), where print was a statement. As of Python 3, print is just a regular function. It returns None, which is falsey, so you'd also need to change your first "and" to an "or".

Thanks for this suggestion - it works great.

    import sys # run: python3 countdown.py 10
    def main(n:int): print(n) or n-1 and main(n-1)
    main(int(sys.argv[1]))
This also works and is definitely more Pythonic:

    _ = [print(n) for n in range(10,0,-1)]

Re: Weekend projects: getting silly with C

#92

Earlier quoted context omitted.

This is in direct contradiction to what uecker says. Can you back up your claim -- for both C and C++? Putting your code in godbolt with -O3 did not remove the print statement for me in either C or C++. But I didn't experiment with different compilers or compiler flags, or more complicated program constructions. https://godbolt.org/z/8nbbd3jPW I've often said that I've never noticed any surprising consequences from U…

In this [1] example GCC hoists, even in C mode, a potentially trapping division above a volatile store. If c=0 you get one less side effect than expected before UB (i.e. the division by zero trap). This is arguably a GCC bug if we agree on the new standard interpretation, but it does show that compilers do some unsafe time travelling transformations. Hoisting the loop invariant div is an important optimization, but i…

Thanks for the example. But again I can't see a problem. The compiler does not actually prove UB in this case, so I suppose this doesn't qualify as applying (mis-) optimizations silently based on UB. Or what did I miss?

Re: Weekend projects: getting silly with C

#93

Earlier quoted context omitted.

This is in direct contradiction to what uecker says. Can you back up your claim -- for both C and C++? Putting your code in godbolt with -O3 did not remove the print statement for me in either C or C++. But I didn't experiment with different compilers or compiler flags, or more complicated program constructions. https://godbolt.org/z/8nbbd3jPW I've often said that I've never noticed any surprising consequences from U…

The implementation can assume that the program does not perpetrate undefined behavior (other than undefined behavior which the implementation itself defines as a documented extension). The only way the program can avoid perpetrating undefined behavior in the statement "x = x / 0" is if it does not execute that statement. Thus, to assume that the program does not invoke undefined behavior is tantamount to assuming tha…

Yeah but do you have an actual instance of "time travel" happening? Without one the issue is merely theoretic discussion of how to understand or implement the standards. If you provide a real instance, the practical impact and possible remedies could be discussed.

Re: Weekend projects: getting silly with C

#94

Earlier quoted context omitted.

In this [1] example GCC hoists, even in C mode, a potentially trapping division above a volatile store. If c=0 you get one less side effect than expected before UB (i.e. the division by zero trap). This is arguably a GCC bug if we agree on the new standard interpretation, but it does show that compilers do some unsafe time travelling transformations. Hoisting the loop invariant div is an important optimization, but i…

Thanks for the example. But again I can't see a problem. The compiler does not actually prove UB in this case, so I suppose this doesn't qualify as applying (mis-) optimizations silently based on UB. Or what did I miss?

Compilers don't prove UB; they assume absence of UB.

That, plus a modicum of reasoning like "if this were to be evaluated, it would be UB" (therefore, let's assume that is not evaluated).

Re: Weekend projects: getting silly with C

#95

Earlier quoted context omitted.

This is in direct contradiction to what uecker says. Can you back up your claim -- for both C and C++? Putting your code in godbolt with -O3 did not remove the print statement for me in either C or C++. But I didn't experiment with different compilers or compiler flags, or more complicated program constructions. https://godbolt.org/z/8nbbd3jPW I've often said that I've never noticed any surprising consequences from U…

The implementation can assume that the program does not perpetrate undefined behavior (other than undefined behavior which the implementation itself defines as a documented extension). The only way the program can avoid perpetrating undefined behavior in the statement "x = x / 0" is if it does not execute that statement. Thus, to assume that the program does not invoke undefined behavior is tantamount to assuming tha…

But does `printf();` return to the caller unconditionally?

This is far from obvious -- especially once SIGPIPE comes into play, it's quite possible that printf will terminate the program and prevent the undefined behavior from occurring. Which means the compiler is not allowed to optimize it out.

Re: Weekend projects: getting silly with C

#96

Earlier quoted context omitted.

In this [1] example GCC hoists, even in C mode, a potentially trapping division above a volatile store. If c=0 you get one less side effect than expected before UB (i.e. the division by zero trap). This is arguably a GCC bug if we agree on the new standard interpretation, but it does show that compilers do some unsafe time travelling transformations. Hoisting the loop invariant div is an important optimization, but i…

Thanks for the example. But again I can't see a problem. The compiler does not actually prove UB in this case, so I suppose this doesn't qualify as applying (mis-) optimizations silently based on UB. Or what did I miss?

The compiler is moving a potentially UB operation above a side effect. This contradicts uecker non-time-traveling-ub and it is potentially a GCC bug.

If you want an example of GCC removing a side effect that happens-before provable subsequent UB: https://godbolt.org/z/PfoT8E8PP but I don't find it terribly interesting as the compiler warns here.

Re: Weekend projects: getting silly with C

#97

Earlier quoted context omitted.

Thanks for the example. But again I can't see a problem. The compiler does not actually prove UB in this case, so I suppose this doesn't qualify as applying (mis-) optimizations silently based on UB. Or what did I miss?

Compilers don't prove UB; they assume absence of UB. That, plus a modicum of reasoning like "if this were to be evaluated, it would be UB" (therefore, let's assume that is not evaluated).

[deleted]

Re: Weekend projects: getting silly with C

#98
post #66

Earlier quoted context omitted.

I think it is UB Edit: actually looks like it is UB in C++ but not C

Why would calling main be UB!? How is crt0 supposed to work?

As the other person pointed out, anything that happens before main is strictly not covered by the C standard.

Even things like “printf” can’t be implemented purely in standard C. Even making a syscall is outside of the scope of the C standard.

Re: Weekend projects: getting silly with C

#99

Earlier quoted context omitted.

Thanks for the example. But again I can't see a problem. The compiler does not actually prove UB in this case, so I suppose this doesn't qualify as applying (mis-) optimizations silently based on UB. Or what did I miss?

Compilers don't prove UB; they assume absence of UB. That, plus a modicum of reasoning like "if this were to be evaluated, it would be UB" (therefore, let's assume that is not evaluated).

Let's not get pedantic about what "proving UB" actually means -- that might lead to philosophic discussions about sentient compilers.

Fact is that in this instance, the compiler did not remove a basic black of code (including or excluding "observeable side-effects" leading up to the point of UB happening). It would not be valid for the compiler to assume that the path is never taken in this case, even assuming that UB never happens, because depending on the the value of the variables, there are possible paths through the code that do not exhibit UB. In other words, "the compiler wasn't able to prove UB".

So this is not an instance of the situation that we are discussing. The emitted code is just fine, unless a division by zero occurs. Handling division by zero is responsibility of the programmer.

Nobody is arguing that UB can lead to weird runtime effects -- just dereference an invalid pointer or whatever.

The issue discussed is that based on assumptions about UB, the compiler emits code that does not correspond to the source in an intuitive way, for example a branch of code is entirely removed, including any observeable side-effects that logically happened before the UB.

Now the point of the GGP poster is probably that the observeable side-effect (the volatile access) does not happen at all because the UB happens first. But I would classify this case differently -- the volatile access is not elided from the branch.

Further more, it might well be that (and let me assume so) the order of the volatile access and the division operation that causes the UB are probably not defined as happening in a strict sequence (because, I'm assuming again as any reasonable standards layman would, UB is not considered a side-effect (that would kinda defeat the point, disallowing optimizations)). So it's entirely valid for the compiler to order the operation that causes the (potential) UB before the volatile access.

Re: Weekend projects: getting silly with C

#100

Earlier quoted context omitted.

That is false. If a compiler determines that some statement has undefined behavior, it can treat it as unreachable, and, transitively, other code before it as unreachable. printf("hello\n"); // this doesn't have to print x = x / 0; // because this is effectively a notreached() assertion

This is in direct contradiction to what uecker says. Can you back up your claim -- for both C and C++? Putting your code in godbolt with -O3 did not remove the print statement for me in either C or C++. But I didn't experiment with different compilers or compiler flags, or more complicated program constructions. https://godbolt.org/z/8nbbd3jPW I've often said that I've never noticed any surprising consequences from U…

Mmmh, how about

    #include 


    int f(int y, int a) {
        int x, z;
        printf("hello ");
        x = y / a;
        printf("world!");
        z = y / a;
        return x+y;
    }
In godbolt, it seems the compiler tends to combine the two printfs together. So if a=0, it leads to UB between the printfs, but that wont happen until after the two printfs. Here the UB is delayed. But will the compiler actually make sure that in some other case, the x/a won't be moved earlier somehow? Does the compiler take any potentially undefined behavior and force ordering constraints around them? ...The whole point of UB is to be able to optimize the code as if it doesn't have undefined behavior, so that we all get the maximum optimization and correct behavior as long as there's no UB in the code.
Post reply on HN