3 ms·
> However, if your program avoids the code paths which trigger undefined behavior, then you are safe. This seems incorrect as demonstrated by the other undefin
by Bjartr 2y ago
> However, if your program avoids the code paths which trigger undefined behavior, then you are safe.
This seems incorrect as demonstrated by the other undefined behavior story I read on HN today[1], the tl;dr of which, as I understood it, is since UB is not allowed, the compiler can elide checks that would protect against UB for the sake of optimization since a correct program wouldn't have caused the UB in the first place and the compiler doesn't have to respect the semantics of incorrect programs.
[1] https://news.ycombinator.com/item?id=41146860 https://news.ycombinator.com/item?id=41146860
- nlewycky 2y agoThe compiler may remove the nullptr check in: ptr->foo = 1; if (ptr == nullptr) return; but it may not remove the nullptr check in: if (ptr == nullptr) return; ptr->foo = 1;
- Bjartr 2y agoI guess I've misunderstood that other story then, thanks.
- thayne 2y agoTo explain why it can be removed in the former: Since it is UB to dereference a null pointer, the compiler can assume that ptr isn't null after it is dereferenced[1]. Therefore, the if condition will always be false. In fact if ptr is null, unless the foo field has a very large offset, the behavior you would probably expect would be for the dereference to segfault before reaching the if, so it doesn't really matter if it is optimized away.
- krackers 2y ago>so it doesn't really matter if it is optimized away. There are cases in which the optimization can result in behavior other than segfaulting, see https://research.swtch.com/ub#null https://research.swtch.com/ub#null
- thayne 2y agoSure. That's why I said "the behavior you would probably expect", but that isn't necessarily what happens.
- ivanbakel 2y ago>is since UB is not allowed, the compiler can elide checks that would protect against UB for the sake of optimization This is incorrect. If a check prevents UB, then the compiler isn't allowed to optimise it away, because the resulting program would then contain UB when it didn't before. Part of the compiler contract is that compilers won't introduce UB - they can only rely on UB already present in the program. Some people think that you can "catch" UB after the fact, but that's an incorrect understanding of how it works. Once UB has occurred, there is no way to go back to a coherent program state and code which would execute after some UB is therefore irrelevant.
- paulddraper 2y agoThe compiler cannot elide checks preventing UB, e.g. null pointer or integer overflow. It can assume those don't happen, and use that assumption to compile code.
- rcxdude 2y agoThe famous case in the linux kernel was caused by a code path which was technically dereferencing a null pointer (but not in practice generating a memory access to the address it pointed to), then doing the check, then dereferencing it "for real". Something like: int *m = &p->foo; if(p) *p->bar; What annoyed people was that while the first line technically dereferences p according to the C standard, all it does in practice is calculate an offset from p. But it was enough for the compiler to rmeove the if and execute the second line unconditionally, making the null pointer dereference "real" (as in the generated assembly would try to read some very low part of memory).
- Bjartr 2y agoAha! I've never had c/c++ as a daily driver, so I missed the significance of taking that reference. Thanks for the explanation!