Undefined behavior causing time travel

One example of this article from the msdn blog made me a ticker:

It says that this function:

void unwitting(bool door_is_open) { if (door_is_open) { walk_on_in(); } else { ring_bell(); // wait for the door to open using the fallback value fallback = value_or_fallback(nullptr); wait_for_door_to_open(fallback); } } 

It can be optimized as follows:

 void unwitting(bool door_is_open) { walk_on_in(); } 

Because calling value_or_fallback(nullptr) is undefined behavior (this was proved earlier in the article).

Now what I don't understand is this: runtime is introduced into undefined behavior only when it reaches this line. Should the incident-before / after-after concept be used in the sense that all observed effects of the first paragraph must be resolved before the runtime enters UB?

+6
source share
3 answers

There is a stream in reasoning.

When the compiler author says: we use Undefined Behavior to optimize the program, there are two different interpretations:

  • most people hear: we identify Undefined Behavior and decide that we can do whatever we want (*)
  • compiler writer meant: we assume Undefined Behavior does not occur

So in your case:

  • dereferencing nullptr - Undefined Behavior
  • So doing value_or_fallback(nullptr) is Undefined Behavior
  • thus executing the else branch undefined behavior
  • this way door_is_open false door_is_open behavior

And since Undefined Behavior does not occur (the programmer swears, it will follow the terms of use), door_is_open necessarily true , and the compiler can go to the else branch.

(*) I'm a little annoyed that Raymond Chen actually put it that way ...

+7
source

It is true that undefined behavior can only occur at run time (for example, dereferencing a pointer that is null). In other cases, the program may be statically "poorly formed, no diagnostics required" (for example, if you add an explicit specialization for the template after it has already been used), which has the same effect: although you cannot argue internally as your program will behave.

Compilers can use UB to β€œoptimize” code generation aggressively. In your case, the compiler sees that the second branch will call UB (I assume it is known statically, even if you did not specify it), and therefore it can assume that this branch is never taken, because it is indistinguishable: If you entered the second branch , then the behavior will be undefined, and it will behave the way you entered the first branch. That way, the compiler can simply view the entire path of the code, which will lead to UB as dead and delete it.

There is no way to prove that something is wrong.

+2
source

What happens when people try to translate common sense into a specification and then interpret the specification without common sense. In my personal opinion, this is completely wrong, but this is what is being done in the process of standardizing the language.

In my personal opinion, the compiler should not optimize code with undefined behavior. But modern postmodern compilers just optimize it. And the standard allows both.

The logic associated with the specific abnormal behavior that you talked about is that the compiler works with branches: if something is undefined in branches, it notes that the whole branch has undefined behavior; and if the branch has undefined behavior, you can replace it with anything.

The worst part is that newer versions of the compiler can break (and break) existing code β€” either not compiling it, or compiling it to stupid things. And "existing code" usually represents a really large amount of code.

0
source

Source: https://habr.com/ru/post/971647/


All Articles