44 ms·
> 14. Okay, but if the line with UB is unreachable (dead) code, then it's as if the UB wasn't there. This one came as a surprise to me. Is this really (uncondi
by bjornsing 4y ago
> 14. Okay, but if the line with UB is unreachable (dead) code, then it's as if the UB wasn't there.
This one came as a surprise to me. Is this really (unconditionally) true? And if so, why? Seems trivial to me to ensure that UB that will never under any circumstance execute will not affect the functioning of an otherwise correct program.
- leni536 4y agoNo, it isn't. The line with UB has to be reached in the execution for the execution to be undefined.
- deleted 4y ago[deleted]
- throwaway294566 4y agoYes, but there is more to it: The compiler may assume that UB will never happen intentionally and optimize out the branch containing UB.
- tsimionescu 4y agoThe compiler isn't required to know if code is unreachable. For example, `if (fermats_last_theorem_is_wrong()) {*p == 1} ` will imply to a modern compiler that p is not NULL in the scope where it is defined, even if we now know that this code will never be reached (assuming that function is correctly written). Or, here is a better example: for(int i=0; i > n;) { } *p=1; Here the compiler will assume that the loop terminates, since non-terminating loops without side-effects are UB in C++. Then, since the loop terminates, p will be dereferenced, so it can be assumed that p is never NULL, so any NULL checks for p in this context can be ellided. This remains valid even if you call this function with an n such that *p=1 is unreachable.
- caf 4y agoIf a some translation unit defines int fermats_last_theorem_is_wrong(void) { return 0; } then a well-defined program can execute that sequence even with p equal to NULL.
- haberman 4y agoThe compiler is required to successfully translate a program that contains unreachable UB, and such a program is conforming if undefined behavior is never reached. This is explicitly spelled out in https://www.open-std.org/jtc1/sc22/wg14/docs/rr/dr_109.html https://www.open-std.org/jtc1/sc22/wg14/docs/rr/dr_109.html > Furthermore, if every possible execution of a given program would result in undefined behavior, the given program is not strictly conforming. A conforming implementation must not fail to translate a strictly conforming program simply because some possible execution of that program would result in undefined behavior. Because foo might never be called, the example given must be successfully translated by a conforming implementation.
- account42 4y agoSad to see this downvoted here. To hammer it in, the consider the following: if(p) *p = 0; *p is undefined if p is NULL. But as long as that statement is never reached when p is NULL that is not a problem. That is trivially true in this case, but the same applies even for more complex relations between the conditional and the undefinedness of the statement. Or put another way, in int * p = 0; if(fermats_last_theorem_is_wrong()) { p = malloc(sizeof(int)); *p = 0; } if(p) { puts("fermats last theorem is wrong\n"); } if(fermats_last_theorem_is_wrong()) { *p == 1; } if(p) { puts("fermats last theorem is wrong\n"); } the compiler is not allowed to remove the if(p) checks and unconditionally print that fermats last theorem is wrong unless it can prove that fermats_last_theorem_is_wrong() is true. In fact, something having undefined behavior and the compiler being allowed to assume that it is unreachable are effectively the same thing. Which is why the spec for std::unreachable() and the compiler-specific intrinsics that preceded it is that it invokes undefined behavior and not more complex wording.
- tsimionescu 4y agoYes, my example with Fermat's last theorem was completely wrong, not sure why it seemed plausible to me when I wrote it, it's obviously just a guard, and it obviously is not UB to have a pointer write behind an If...
- mnw21cam 4y agoPresumably (and really don't take my word for this - I'm a Java bod) the p dereference needs to come before the p NULL check for the p NULL check to be ignored. (Otherwise, how can a NULL check protect a dereference.) And therefore, the NULL check would be unreachable as well, and therefore never executed and not relevant. Please correct my misunderstanding...
- adgjlsfhk1 4y agoNope, the C spec is just really bad. The NULL check is allowed to be removed.
- mnw21cam 4y agoSo the pseudocode: if (p is not NULL) { dereference p } is UB? That's crazy, but not actually unbelievable.
- dllthomas 4y agoI am not certain of the language lawyer side of it, but no reasonable (or even moderately unreasonable) compiler would omit that check because of the guarded behavior; way too much code would break, probably including that compiler's standard library.
- UncleMeat 4y agoThe poster is just completely wrong. A compiler is required to emit the branch in this case.
- tsimionescu 4y agoThere's no misunderstanding on your part, just some muddled thinking on my part for the first example.
- account42 4y ago> For example, `if (fermats_last_theorem_is_wrong()) {*p == 1} ` will imply to a modern compiler that p is not NULL in the scope where it is defined, even if we now know that this code will never be reached (assuming that function is correctly written). No it won't. Otherwise all precondition checks would be meaningless. The compiler can only assume that it isn't null when fermats_last_theorem_is_wrong() is true. If the compiler cannot reason about fermats_last_theorem_is_wrong() then it can't just ignore it. > for(int i=0; i > n;) { } *p=1; > Here the compiler will assume that the loop terminates, since non-terminating loops without side-effects are UB in C++. Infinite loops are UB precisely because otherwise the compiler would NOT be able to reason about this. So yes, here the comiler can assume that p is a valid pointer - but only because it can also assume that *p=1 is NOT dead. This falls entirely under the umbrella of behavior not being defined at all after you execute undefined behavior (the infinite loop). > This remains valid even if you call this function with an n such that *p=1 is unreachable. Only because the loop itself is undefined behavior in that case.
- AstralStorm 4y agoThat is not an infinite loop, it has an exit condition on i > n. An infinite loop is not UB. It is well defined. Just the definition of a terminating loop is a bit funny. > An iteration statement whose controlling expression is not a constant expression, that performs no input/output operations, does not access volatile objects, and performs no synchronization or atomic operations in its body, controlling expression, or (in the case of a for statement) its expression, may be assumed by the implementation to terminate. Plus: > An omitted controlling expression is replaced by a nonzero constant, which is a constant expression. The compiler may only use as-if rule here if it can deduce the value is constant or limited. It can (but does not have to) assume the loop will end due to the conditional and lack of the other features. Which is still defined behavior. (since C11 at least, probably earlier)
- haberman 4y ago> An infinite loop is not UB. It is well defined. From C11: > An iteration statement whose controlling expression is not a constant expression,156) that performs no input/output operations, does not access volatile objects, and performs no synchronization or atomic operations in its body, controlling expression, or (in the case of a for statement) its expression-3, may be assumed by the implementation to terminate.157) > 157)This is intended to allow compiler transformations such as removal of empty loops even when termination cannot be proven. This makes infinite loops UB unless they fall under one of these exceptions (constant controlling expression, performs I/O, accesses volatile objects or atomics).
- deleted 4y ago[deleted]
- zajio1am 4y agoThat (or more general 13-16) also does not make sense to me. Most UB (although perhaps not all, so it may be true for some obscure UB, or ones with constant arguments) are undefined for specific dynamic conditions, so it is not 'line with UB' but more 'line with UB under specific program state', e.g. expression 'a / b;' has UB if b == 0, 'a->item' has UB if b == NULL, or 'a * 4' has UB if a is int and large enough to cause overflow. If the expression is not executed, the dynamic conditions are not there, and therefore there is no UB.
- mattarm 4y agoFYI, an ODR violation (https://en.wikipedia.org/wiki/One_Definition_Rule https://en.wikipedia.org/wiki/One_Definition_Rule) is an example of non-dynamic UB.
- asgeir 4y agoThe compiler may make optimization decisions based on the presence of UB so the code only needs to be present. The compiler can't know that an arbitrary part of a function is unreachable if, for instance, that code path is controlled by a parameter or global state since that equates to solving the halting problem. https://blog.llvm.org/2011/05/what-every-c-programmer-should-know_14.html https://blog.llvm.org/2011/05/what-every-c-programmer-should...
- haberman 4y agoI don't think this is correct. If UB is never invoked, the program is conforming and must be translated/executed correctly. DR #109 specifically states that a compiler may not fail to translate a strictly conforming program just because some possible executions could trigger UB: https://www.open-std.org/jtc1/sc22/wg14/docs/rr/dr_109.html https://www.open-std.org/jtc1/sc22/wg14/docs/rr/dr_109.html Edit: downvoters, please explain your disagreement. I cited a primary document in support of my position.
- asgeir 4y agoThe undefined behavior can still affect code generation even though the program gets translated. It's just that the generated code may not do exactly what you expect it to do because the presence of undefined behavior allowed the compiler to make assumptions which may surprise you. edit: It seems that I am incorrect
- haberman 4y agoDR 109 allows that a program may be strictly conforming even if some possible executions of the program invoke UB: > A conforming implementation must not fail to translate a strictly conforming program simply because some possible execution of that program would result in undefined behavior. This text specifically allows for the case that a program is strictly conforming even if there is a possible execution that invokes UB. If a program is strictly conforming, it must produce the correct behavior. I would challenge you to show an example where GCC or Clang will break the correctness of a program on account of UB that is not reached during program execution. Here is an example where GCC and Clang specifically respects the correctness of a program as long as it does not reach the UB: https://godbolt.org/z/befWah77W https://godbolt.org/z/befWah77W
- Semaphor 4y agoThis [0] is the linked article for that point, I don’t do systems programming, so I can’t say I understand if this is trivial or not ;) [0]: https://www.ralfj.de/blog/2020/07/15/unused-data.html https://www.ralfj.de/blog/2020/07/15/unused-data.html
- account42 4y agoThe linked article argues that making storing invalid values in the bool undefined (rather than just using such bools) is important because otherwise the compiler is not allowed to move the code (without adding appropriate checks that it would have been reached). The compiler can only make that transformation if it has no effect on any cases where the code wasn't reached at all, which includes not introducing any new UB in those cases. Predrag conclusion that this means having UB in dead code matters is simply wrong.
- nickitolas 4y agoThe blog post does not support the assertion that unreachable UB which is not executed is a problem. It starts by saying > The Rust compiler has a few assumptions that it makes about the behavior of all code. Violations of those assumptions are referred to as Undefined Behavior. Then it says > In other words, even just constructing, for example, an invalid bool, is Undefined Behavior—no matter whether that bool is ever actually “used” by the program. The blog post is talking about how just creating an invalid/trap/niche representation for a value is UB. The act of creating the value is the UB here, not the usage. So, in this case the UB is most definetely reachable and executing (The act of creating the invalid value).
- tempodox 4y agoCode that represents UB doesn't have to be executed for the compiler to react to it.
- AstralStorm 4y agoMore specifically, the linker, not the compiler. The compiler has to respect DR 106. The linker, however, is allowed to write garbage into code.
- gpderetta 4y agothe standard does not distinguish the linker from the compiler.