3 ms·
It makes no difference to initialize the pointer to nullptr, actually. Also, neither GCC, nor Clang produce a single warning with -Wall here. Ideally, I think,
by dale_glass 6y ago
It makes no difference to initialize the pointer to nullptr, actually. Also, neither GCC, nor Clang produce a single warning with -Wall here.
Ideally, I think, the pointer should be null and one would get some sort of error to the effect 'you didn't initialize that pointer, dummy'.
It would be somewhat expected if it was initialized randomly and just jumped to some completely random part of the code. That wouldn't be great, but at least it would crash most of the time.
But what clang is doing here is engaging in bizarre evil genie deduction:
1. Undefined behavior isn't a thing
2. So Do() can't be UB
3. The only way for it not be UB, is for NeverCalled() to get called somehow, though it never is.
4. Perhaps something unseen does that from outside this compilation unit.
5. So we're going to make that assumption out of thin air, and not say a single peep about it.
- Tarean 6y agoI think for clang that's the purpose of -fsanitize=undefined and -fsanitize=nullability. If some undefined behavior isn't caught by the sanitizer, clang doesn't use it for optimization.
- jcranmer 6y ago> 1. Undefined behavior isn't a thing 2. So Do() can't be UB 3. The only way for it not be UB, is for NeverCalled() to get called somehow, though it never is. 4. Perhaps something unseen does that from outside this compilation unit. 5. So we're going to make that assumption out of thin air, and not say a single peep about it. That's not what how the chain works. This is the actual chain of reasoning: 1. What can Do point to? 1a. It's a global variable whose address is never leaked to anybody outside of the current translation unit. Therefore, all of the assignments I see to this variable constitute the complete set of possible values. 1b. The assignments are the global initialization to nullptr and the assignment to EraseAll. 1c. Therefore, the only possible values of Do are {nullptr, EraseAll}. 2. When I call Do, what can happen? 2a. If Do is EraseAll, I emit a call to EraseAll directly. 2b. If Do is nullptr, it is undefined behavior, and therefore I can replace this with any other behavior. 3. Okay, let's replace a call to a function pointer with a direct call to EraseAll, is that legal? 3a. If Do is EraseAll, yes, I'm calling the same function. 3b. If Do is nullptr, yes, it's undefined behavior, and calling a function is a valid refinement to undefined behavior. 4. It's safe to do so, therefore I'm going to call EraseAll. It's not that undefined behavior doesn't exist, it's that any behavior is a valid refinement to undefined behavior. It would equally be a valid refinement to replace a null function call with a crash. Indeed, comment out NeverCalled, and the code is replaced with a SIGILL crash (it executes the ud2 instruction instead of calling an undefined pointer).