6 ms·
I agree that "ignoring the situation completely with unpredictable results" is also bad, although I might be able to see some situations where it would be diffi
by quelsolaar 6y ago
I agree that "ignoring the situation completely with unpredictable results" is also bad, although I might be able to see some situations where it would be difficult to write a spec in a clear way that was more precise.
However the problem isn't just that compilers ignore UB, they actively make use of it! In the example:
if(p == NULL)
write_out_error_message_and_exit(0);
*p = 0;
The NULL check is removed by the compiler because of the undefined behavior of writing to NULL. Instead of ignoring that the code might write to NULL, the compiler does the opposite, and assumed that p cant be NULL.
- saagarjha 6y agoThat code should be fine, the problem arises when the dereference comes first and/or control flow doesn’t leave before the dereference.
- quelsolaar 6y agoIt should be fine, but it isnt. The Linux kernel has had problems with this kind of code, because the flow control indicates that the dereferencing will happen irregardless of the if statement. Its order independent. If the function had a "noreturn" qualifier, or an "else" that could tell the compiler that hitting the if statement will mean that the dereference never happens, then the compiler would not break this code. I dont want to sound like I'm showing off of or putting down your comment. Your comment is 100% reasonable. Its an example of how the spec is not reasonable, and it doesn't do what any reasonable person would expect.
- saagarjha 6y agoNo, I meant what I said. The famous kernel “null pointer check removal” bug was in code of the form void foo(int *bar) { int baz = *bar; if (!bar) { return; } /* do stuff with baz */ } which you can see provably invokes undefined behavior if bar is NULL. In the case you provided where the dereference comes after a function call the compiler cannot optimize out the check unless it can prove that control flow returns, which you claim it does not (it exits). noreturn and such are there to improve these cases and make them explicit to the compiler, but in the absence of information it must be conservative as there are many ways for a function call to never return, one of which is exit(3). Optimizing this case would be incorrect (i.e. a compiler bug) and this happens to be one of the reasons why function calls often serve as a barrier to optimizations in C.
- MauranKilom 6y ago> If the function had a "noreturn" qualifier, or an "else" that could tell the compiler that hitting the if statement will mean that the dereference never happens, then the compiler would not break this code. If the compiler can't prove that the function returns, it's not allowed to assume that it does. I challenge you to cook up an example on godbolt.org that shows the behavior you claim. But take note of the difference between https://godbolt.org/z/sWYEYc https://godbolt.org/z/sWYEYc and https://godbolt.org/z/3E7hbz https://godbolt.org/z/3E7hbz
- aw1621107 6y ago> However the problem isn't just that compilers ignore UB, they actively make use of it! I think this might depend on how you interpret the standard and/or compiler's actions. If you believe "ignoring the situation completely" means "ignore precisely those statements that invoke UB and leave everything else intact", then the transformations done by compilers can look like actively taking advantage of UB beyond what might be implied by the standard. If you think "ignoring the situation completely" allows "inspect the set of program executions, discard those in which UB is invoked, and optimize based on the remaining possible executions", then the mere act of "ignoring UB" can't really be distinguished from "actively making use of UB"; the actions are one and the same.