Consider this test case: $ cat test.cc struct MyClass; struct ptr { MyClass* get() { return t; } MyClass* t; }; struct MyClass { void call(); }; void MyClass::call() { *(char*)(nullptr) = 1; } static void intermediate(ptr p) { p.get()->call(); } int main() { intermediate(ptr{new MyClass}); } $ g++ -g -O0 test.cc $ ./a.out Segmentation fault (core dumped) $ g++ -g -Og test.cc $ ./a.out $ g++ -g -O1 test.cc $ ./a.out $ g++ -g -O2 test.cc $ ./a.out Segmentation fault (core dumped) $ g++ -g -O3 test.cc $ ./a.out Segmentation fault (core dumped) $ g++ -g -Ofast test.cc $ ./a.out Segmentation fault (core dumped) It is somehow unexpected that this code is optimized away only at -Og -O1, but not at very high or very low optimization levels. I would even say although the code is of course invalid, it should not be optimized away, as it might be a debug-code that intentionally taps.
This code is not invalid but undefined at runtime. This means anything can happen. In this case, GCC optimizes it away. It is better to use __builtin_trap or __builtin_abort/std::abort if you want to get a trap going on.
I debugged a bit in when we decide this function is const. That appears to be in gcc/ipa-fnsummary.c: /* Return true if T is a pointer pointing to memory location that is local for the function (that means, dead after return) or read-only. */ bool points_to_local_or_readonly_memory_p (tree t) { /* See if memory location is clearly invalid. */ if (integer_zerop (t)) return flag_delete_null_pointer_checks; if (TREE_CODE (t) == SSA_NAME) return !ptr_deref_may_alias_global_p (t); if (TREE_CODE (t) == ADDR_EXPR) return refs_local_or_readonly_memory_p (TREE_OPERAND (t, 0)); return false; } And indeed the "problem" can be fixed by using -fno-delete-null-pointer-checks. From the documentation in gcc/doc/invoke.texi I would never have guessed what this option does here: "@item -fdelete-null-pointer-checks @opindex fdelete-null-pointer-checks Assume that programs cannot safely dereference null pointers, and that no code or data element resides at address zero. This option enables simple constant folding optimizations at all optimization levels. In addition, other optimization passes in GCC use this flag to control global dataflow analyses that eliminate useless checks for null pointers; these assume that a memory access to address zero always results in a trap, so that if a pointer is checked after it has already been dereferenced, it cannot be null." It is only supposed to remove checks for null-pointer, not the other way round...
-fdelete-null-pointer-checks is generally an option that tells whether it is possible to have objects at address 0 (-fno-delete-null-pointer-checks) or not. So the use above seems to be correct. Anything can happen on UB and NULL pointer dereference is UB, so I don't see why you expect anything in particular.