see also https://www.chiark.greenend.org.uk/~sgtatham/mp/ Metaprogramming custom control structures in C by Simon Tatham
Weekend projects: getting silly with C
61–70 of 118 posts
Re: Weekend projects: getting silly with C
#62This features the construct switch(k) { if (0) case 0: x = 1; if (0) case 1: x = 2; if (0) default: x = 3; } which is a switch where you don't have to write break at the end of every clause. #define brkcase if (0) case That might be worth using. Compilers won't love the control flow but they'll probably delete it effectively.
Surely the following would work just as well? #define brkcase break;case kinda defeats the purpose of the macro even.
Incidentally, what happens if you use your brkcase as the first case?
I don't find either particularly exciting - a macro that would append break to the current case feels better
Re: Weekend projects: getting silly with C
#63Earlier quoted context omitted.
> Also broken compilers [1]. The issue you linked to is not a counter example because, as the poster said, g may terminate the program in which case that snippet does not have undefined behaviour even if b is zero. The fact that they bothered to mention that g may terminate the program seems like an acknowledgement that it would be valid to do that time travelling if it didn't. > Note that blog post is correct about…
The poster is me. You are right that this is not an example for time-travel. There aren't really good examples for true time travel because compilers generally do not do this. But my point is that with compilers behaving like this, people might confuse this for time-traveling UB. I have certainly met some who did and the blog posts seems to have similar examples (but I haven't looked closely now). Note that I am a me…
The lack of clarity in earlier standards made it impossible to deal with code incrementally, since all the unknown execution paths could potentially breach back in time and smash your semantics.
Re: Weekend projects: getting silly with C
#64Earlier quoted context omitted.
Surely the following would work just as well? #define brkcase break;case kinda defeats the purpose of the macro even.
I think the behavior is slightly different since this one breaks the above case, and the other one only omits its case from fallthrough Incidentally, what happens if you use your brkcase as the first case? I don't find either particularly exciting - a macro that would append break to the current case feels better
switch (a) {
brkcase 0: foo();
case 1: bar();
}
so in a sense the `if (0) case` trick also affects the previous case, not the current one. But that one also falls apart when there are multiple statements under the brkcase.Re: Weekend projects: getting silly with C
#65Earlier quoted context omitted.
I'm inclined to trust Raymond Chen and John Regehr on these matters, so if you assert that they're incorrect here then a source to back up your assertion would help your argument.
I am a member of WG14. You should check the C standard. I do not see how "time-travel" is a possible reading of the definition of UB in C. We added another footnote to C23 to counter this idea: https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3220.pdf "Any other behavior during execution of a program is only affected as a direct consequence of the concrete behavior that occurs when encountering the erroneous or non…
- if a compiler finds that condition A would lead to UB, it can assume that A is never true - that fact can "backpropagate" to, for example, eliminate comparisons long before the UB.
Here is an older discussion: https://softwareengineering.stackexchange.com/q/291548
Is that / will that no longer be true for C23? Or does "time-travel" mean something else in this context?
Re: Weekend projects: getting silly with C
#66This 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…
I don't think I've ever thought of explicitly calling main(). Made me chuckle.
Edit: actually looks like it is UB in C++ but not C
Re: Weekend projects: getting silly with C
#67I've seen this in the wild, particularly with macros.
#define assert(c) if (!c) ...
if (foo) assert(...);
else bar(); // oops!Re: Weekend projects: getting silly with C
#68Earlier quoted context omitted.
I am a member of WG14. You should check the C standard. I do not see how "time-travel" is a possible reading of the definition of UB in C. We added another footnote to C23 to counter this idea: https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3220.pdf "Any other behavior during execution of a program is only affected as a direct consequence of the concrete behavior that occurs when encountering the erroneous or non…
So maybe we have different definitions of "time travel". But I recall that - if a compiler finds that condition A would lead to UB, it can assume that A is never true - that fact can "backpropagate" to, for example, eliminate comparisons long before the UB. Here is an older discussion: https://softwareengineering.stackexchange.com/q/291548 Is that / will that no longer be true for C23? Or does "time-travel" mean some…
There is unconditional use of a pointer b, which is UB if b is null. However, there is an earlier branch that checks if b is null. If we expected the UB to "backpropagate", the compiler would eliminate that branch, but both gcc and clang at O3 keep the branch.
However, both gcc and clang have rearranged the side effects of that branch to become visible at the end of the function. I.e. if b is null, it's as if that initial branch never ran. You could observe the difference if you trapped SIGSEGV. So even though the compiler didn't attempt to "time-travel" the UB, in combination with other allowed optimizations (reordering memory accesses), it ended up with the same effect.
Re: Weekend projects: getting silly with C
#69Earlier quoted context omitted.
The poster is me. You are right that this is not an example for time-travel. There aren't really good examples for true time travel because compilers generally do not do this. But my point is that with compilers behaving like this, people might confuse this for time-traveling UB. I have certainly met some who did and the blog posts seems to have similar examples (but I haven't looked closely now). Note that I am a me…
I didn't notice that section when I last read through C23, but I'm very glad to see it. Reining in UB is one of the hardest problems I've had to deal with, and being able to say operations are defined up to the point of UB makes my job so much easier. The lack of clarity in earlier standards made it impossible to deal with code incrementally, since all the unknown execution paths could potentially breach back in time…
Re: Weekend projects: getting silly with C
#70Earlier quoted context omitted.
I am a member of WG14. You should check the C standard. I do not see how "time-travel" is a possible reading of the definition of UB in C. We added another footnote to C23 to counter this idea: https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3220.pdf "Any other behavior during execution of a program is only affected as a direct consequence of the concrete behavior that occurs when encountering the erroneous or non…
So maybe we have different definitions of "time travel". But I recall that - if a compiler finds that condition A would lead to UB, it can assume that A is never true - that fact can "backpropagate" to, for example, eliminate comparisons long before the UB. Here is an older discussion: https://softwareengineering.stackexchange.com/q/291548 Is that / will that no longer be true for C23? Or does "time-travel" mean some…
int foo(int x)
{
printf("%d\n", x);
fflush(stdout);
return 1 / x;
}In the following example
int foo(int x)
{
if (x) bar(x);
return 1 / x;
}the compiler could indeed remove the "if" but not because it were allowed to assume that x can never be zero, but because 1 / 0 can have arbitrary behavior, so could also call "bar()" and then it is called for zero and non-zero x and the if condition could be removed (not that compilers would do this)