
Undefined behavior can result in time travel - ingve
http://blogs.msdn.com/b/oldnewthing/archive/2014/06/27/10537746.aspx
======
JoshTriplett
Personally, I find it painful that the compiler detects this kind of undefined
behavior, and silently uses it for optimization, rather then stopping and
emitting an error. In the printf example, the compiler could trivially emit an
error saying "NULL check on p after dereference of p", and that would catch a
large class of bugs. (Some static analysis tools check for exactly that.)
Similarly, a loop access statically determined to fall outside the bounds of
the array should produce an error.

~~~
pascal_cuoq
Excuses are provided in [http://blog.llvm.org/2011/05/what-every-c-programmer-
should-...](http://blog.llvm.org/2011/05/what-every-c-programmer-should-
know_21.html) . But they are just that, excuses.

To summarize at least one of them, the compiler doesn't really see it as
“detecting undefined behavior and optimizing accordingly”. It sees it as doing
the right thing for all defined behaviors. The sort of imprecise analysis it
does lead it to consider plenty of possible undefined behaviors, many of which
cannot happen in real executions. It ignores these as a matter of fact, but
reporting them would not tell the programmer anything it doesn't know, and
would be perceived as noise.

On the example for (int i=1; i==0; i++) …, the compiler does not infer that i
eventually overflows (undefined behavior). It infers that i is always
positive, and thus that the condition is always false.

~~~
bdamm
Overflows are not undefined. They are overflows. Maybe I want to overflow on
purpose. Your for loop (int is signed) will complete assuming the body of the
loop doesn't manipulate i, and given enough time.

~~~
marvy
unsigned overflows are well defined, but signed ones are not

~~~
mtdewcmu
That makes sense (sort of). Better to use unsigned if you are trying to do
modular arithmetic.

Signed integers have some weirdness attached. The number that's one followed
by all zeroes in binary (INT_MIN in limits.h) is defined as negative, because
the sign bit is set. But, the rules for 2's complement arithmetic predict that
-INT_MIN == INT_MIN. So it's not a normal number.

~~~
mikeash
Similarly amusing, INT_MIN / -1 will throw a "division by zero" on Intel CPUs,
even though there isn't a zero anywhere in sight. INT_MIN * -1 is fine, of
course (according to the CPU, even if not the language spec).

~~~
oofabz
INT_MIN / -1 works fine for me on amd64 with gcc 4.9. It produces INT_MIN,
just as you would expect. INT_MIN * -1 is also INT_MIN.

    
    
      #include <limits.h>
      #include <stdio.h>
      
      void main(void) {
              int x = INT_MIN;
              printf("INT_MIN = %d\n", x);
              printf("INT_MIN * -1 = %d\n", x * -1);
              printf("INT_MIN / -1 = %d\n", x / -1);
      }

~~~
lauraura
Your arithmetic is being optimized out by the compiler;
[https://ideone.com/KmTSUB](https://ideone.com/KmTSUB) crashes for example.

~~~
e12e
Looks like you're right (re my sibling comment above):

    
    
        $ gcc -std=c99 -S intmin.c -o intmin.s
        $ clang -std=c99 -S intmin.c -o intmin.sc
        $ grep -i div intmin.s*
        intmin.sc:      idivl   %esi
    

(No idivl in the gcc version)

------
ben0x539
From the linked John Regehr blog post
([http://blog.regehr.org/archives/767](http://blog.regehr.org/archives/767)):

    
    
      Nick Lewycky submitted this code:
      
      #include <stdio.h>
      #include <stdlib.h>
      
      int main() {
        int *p = (int*)malloc(sizeof(int));
        int *q = (int*)realloc(p, sizeof(int));
        *p = 1;
        *q = 2;
        if (p == q)
          printf("%d %d\n", *p, *q);
      }
    

This got my attention for a lot longer than the OP, because it maintains the
surprising behavior (prints different values for * p and * q even if p == q)
if you move the assignments inside the if-statement:
[http://codepad.org/PBUAgnQq](http://codepad.org/PBUAgnQq)

I'm told that a pointer passed to realloc has to be assumed to be invalidated,
even if it's exactly equal to another pointer that you know is valid, but it's
hard to wrap my head around that and I certainly didn't get that out of
looking at the C89 standard.

~~~
eridius
Under what circumstances does it print different values? I just tried your
code locally with clang on OS X, and I get `2 2` at all optimization levels.

If the compiler is indeed allowed to assume that the pointer passed to
realloc() becomes invalid, then I would expect it to actually optimize out
that entire if-check, under the assumption that the `*p` is undefined
behavior, and therefore that `p == q` must never be true.

Getting different values for the print if you move the assignments inside the
if statement suggests to me that a) it's assuming the pointers don't alias,
and therefore b) that it assumes it doesn't have to read the values back out
of the pointer when printing them but can just reuse the values it knows it
wrote to the pointer. But if it assumes the pointers don't alias, then I would
think it would assume that means `p == q` can't be true.

~~~
spc476
It's undefined behavior, so the compiler is free to do pretty much anything it
wants. It can always assume it's true; it can always assume it's false; it can
omit code to return true 50% of the time.

~~~
eridius
In theory it can do whatever it wants. In practice, it generally just assumes
undefined behavior won't happen, and will therefore assume that any conditions
that could lead to undefined behavior are false (and prune any dead code that
results from those assumptions).

The other thing that can happen is LLVM has the concept of a undefined value,
which is distinct from undefined behavior. Undefined values may be unknown,
but the compiler can assume that any possible value still results in defined
behavior, and optimize accordingly. As an example, an un-initialized stack
variable has an undefined value, but various operations on it may still result
in defined behavior regardless of the value.

You can read more about undefined values in LLVM at
[http://llvm.org/docs/LangRef.html#undefined-
values](http://llvm.org/docs/LangRef.html#undefined-values) if you're
interested.

------
missblit
I've had a bug that seemed like time travel before. I was doing something
weird with threading and unix pipes. Then I was trying to print out some debug
information, but an unrelated string got printed out instead.

This unrelated string never should have been printed to the pipe in question
in the first place (!), and also didn't even exist at the point where it got
printed out - being calculated a few lines down (!!).

The issue went away when I fixed a seemingly unrelated bug (that didn't look
like it involved undefined behavior at all), but it all still gives me
nightmares to this day D:

The C is dark and full of terrors.

~~~
Spearchucker
Heisenbugs. They're devilspwan. I encountered something similar in VB.Net of
all languages. Visual Studio Express has a bug in that adding a custom control
to a Windows Form seems to drop the line from the auto -generated code that
initializes that control. The error message said that it tried to assign a
string to an integer or something, in code that executes way after the form is
initialized. Took me ages to work that one out...!

[http://en.m.wikipedia.org/wiki/Heisenbug](http://en.m.wikipedia.org/wiki/Heisenbug)

------
codyb
This is a great article. I'm really enjoying some of the compiler
optimizations I'm seeing. It's an area not oft explored for me.

However, I'm having a bit of an issue understanding what the compiler is doing
here, at the beginning of the article.

If someone can explain, it'd be appreciated.

FTA:

 _A post-classical compiler, on the other hand, might perform the following
analysis:_

    
    
        The first four times through the loop, the function might return true.
        When i is 4, the code performs undefined behavior. Since undefined behavior lets me do anything I want, I can totally ignore that case and proceed on the assumption that i is never 4. (If the assumption is violated, then something unpredictable happens, but that's okay, because undefined behavior grants me permission to be unpredictable.)
        The case where i is 5 never occurs, because in order to get there, I first have to get through the case where i is 4, which I have already assumed cannot happen.
        Therefore, all legal code paths return true. 
    

_As a result, a post-classical compiler can optimize the function to_

    
    
      bool exists_in_table(int v)
      {
          return true;
      }
    

I see might return true. Ignored. And never happens.

I think the idea is that since i=4 is ignored, and the loop goes while i <= 4,
you can never reach the return false statement? That's my understanding, I'm
just not confident on it.

~~~
dkersten
The first example took me a few reads to get too.

Basically, since _i=4_ causes undefined behaviour, the compiler assumes that
it can't possibly happen[1] and the only way that it can't happen is if one of
the prior (i < 4) checks were true.

Therefore all legal code paths (ie all code paths that don't result in
undefined behaviour) return true.

So it optimises it to simply return true. Because otherwise i=4 would have
happened, but that's undefined, so impossible[1]

[1] but if it does happen, that's ok, because that would be undefined
behaviour, which allows the compiler to do whatever it feels like anyway

~~~
codyb
Okay, yea, that makes sense.

Awesome. Thanks.

------
vinkelhake
Great article.

Articles like this and the three-part series about undefined behavior on the
LLVM blog [0] ought to be required reading for anyone who still has the
impression that C is "portable assembler".

[0] [http://blog.llvm.org/2011/05/what-every-c-programmer-
should-...](http://blog.llvm.org/2011/05/what-every-c-programmer-should-
know.html)

~~~
dllthomas
The legitimate use of the notion that "C is portable assembler" is as a
reflection of the purposes to which it is put. Unquestionably there can be an
arbitrarily large gulf between the C code and the micro-semantics of the
generated assembly. Though it doesn't stop there - there's always _some_ space
even between the machine code and what actually happens - arguably larger
these days, with out-of-order execution and similar optimizations.

------
ChuckMcM
I've read this a couple of times. And I can't help shaking the feeling that a
'post classical compiler' is, to my way of thinking, broken.

The compiler should, again in my opinion, in the presence of undefined
behavior simply spit out an error and say "Behavior here is undefined, fix
it." that any compiler could recognize some undefined behavior in the way the
code was written, and exploit that as an "optimization" boggles my mind.

------
ambrop7
I'm wondering if the exception is when the preceding ring_bell() function
never returns? Then there is no undefined behavior since the line with
undefined behavior is never reached.

So one could conclude that the compiler has to prove termination of
ring_bell() before performing the optimization discussed, which is impossible
for just any external function.

~~~
mihai_ionic
According to the C++11 standard, the compiler may assume that any loop
terminates, so unless you mark ring_bell as [[noreturn]], the code will be
assumed reachable.

Furthermore, when undefined behaviour is invoked anywhere within a program,
the whole program is undefined.

~~~
robryk
As far as I remember, you are wrong: a loop with side effects (either external
IO or volatile reads/writes) is allowed to never terminate.

------
ars
Does this article mix undefined with implementation defined?

It's assuming undefined means the code can never occur (so it removed that
code), but aren't most programmers assuming the code _can_ occur but something
weird will be done?

~~~
tel
Those aren't different. The idea is entirely that "implementation defined" is
vague enough to allow definitions such as "do whatever it takes to simulate
the undefined behavior as never even having occurred". That opens up new
optimization techniques as shown.

~~~
mikeash
"Implementation defined" means that the compiler can do whatever it feels
like, but that it must choose something specific _and that this must be
documented_ , and any program that invokes implementation defined behavior is
perfectly well formed. "Undefined" means that the compiler can do whatever it
feels like and it can be completely inconsistent and documented nowhere, and
any program which invokes it is ill-formed, and programs can be assumed not to
invoke it.

------
TheLoneWolfling
It would make things so much less error-prone if the undefined behavior only
could affect anything touched by the undefined statement, in a cascading
fashion, and only forwards.

So if you did the following:

    
    
        int data[1];
        int foo = data[1];
        printf("Bar");
    

foo would be undefined, but you know that "Bar" would be printed regardless.

My question is: are there any legitimate optimizations that would be prevented
by this?

~~~
DSMan195276
Yes, the biggest problem would be that undefined behavior would have to allow
the program to keep going. For example, if reading 'data[1]' may seg-fault
(And there are valid situations it could), then the compiler would need to
prevent that seg-fault or else "Bar" wouldn't print.

It's also worth noting that your trivial example would result in basically
everything be removed, but most non-trivial examples don't do that. The most
common 'optimization' from undefined-behaviour is that the compiler doesn't
need to check for those conditions and can let whatever will happen happen,
and that only works if it's defined in a program-wide anything-goes sense. If
it's defined on a local sense, then if say 'data' was passed-in as a parameter
instead of declared, the compiler would have to insert a NULL check to make
sure no undefined-behaviour happens and the program doesn't crash (So that
"Bar" prints). By defining undefined-behaviour like it is, there's no
requirement for the compiler to do a NULL check, it can instead just assume
the programmer will never let it happen and produce code with that in mind.
Same thing with integer overflow and similar cases (Though things get a bit
hairier there).

~~~
TheLoneWolfling
For the argument checking case, the compiler can turn the function into two
functions, a wrapper function that checks arguments and calls the internal
function, and an internal function that doesn't check its arguments, calling
the two as appropriate. Then only export the wrapper function, but allow code
that the compiler knows to not do things that might be undefined to call the
inner function directly.

(This was actually how I'd always assumed compilers optimized publicly-
accessible functions, and was quite surprised when I found out they didn't.)

If you're willing to get weird, you can even optimize it into one function
with two entry points on some platforms.

Also, personally segfaults shouldn't exist, or rather not in their current
uncatchable form. Everything that is potentially recoverable should be able to
be caught. So the compiler would wrap the access to data[1] in a try/catch
block, which doesn't hinder performance in the common case, while retaining
"good" behavior in the bad one. (It can do so because it is not writing
anything, just reading it.) Haven't ever used it, but look at
[https://code.google.com/p/segvcatch/](https://code.google.com/p/segvcatch/)
for something similar.

~~~
mikeash
It's generally easy to catch segfaults. On UNIX-like systems, you can just set
a signal handler for SIGSEGV. Other systems generally provide similar
functionality.

The problem isn't that they're hard to catch, it's that it's virtually
impossible to proceed in any sort of sane manner once a segfault has happened.
You have no idea how much state got corrupted before the segfault actually
happened. You have no idea what cleanup the functions currently on the stack
expect to be able to accomplish before they return. You have no idea what kind
of inconsistent state the data structures in memory are in.

If you're really lucky, everything is fine and you can keep on going. If
you're not so lucky, stuff is corrupted and you just crash again the moment
you try to resume, and again, and again, in an infinite segfault loop. If
you're really unlucky, your program doesn't crash again, but proceeds with
corrupted data, saving it out to disk and displaying it to the user and
causing all sorts of havoc.

I actually helped out a little bit with a similar system:

[https://www.plausible.coop/blog/?p=263](https://www.plausible.coop/blog/?p=263)

Although instead of throwing an exception, it simply tried to proceed to the
next instruction.

The whole thing was done as a joke for April Fools' Day, because it's a
completely awful idea. Making it throw an exception instead of continuing
immediately doesn't really make it better.

I agree in general that segfaults shouldn't exist, but your proposed solution
is frightening. Segfaults shouldn't exist because the compiler enforces bounds
checking, safe memory management, and other such things that ensure that your
program never attempts to access memory it can't access. Once the attempt is
made, it's far too late to do anything but crash.

------
phkahler
I don't think the compiler is supposed to treat an entire function behavior as
undefined just because a pointer was dereferenced without checking for NULL.
The array indexing example may be valid, but the second one is probably not.
It is possible to have a pointer to zero, and failing to check for that
condition should not cause the compiler to assume the pointer is non-zero in
subsequent lines of code.

~~~
regehr
It's not just the function that becomes undefined, it's the entire program.
There's literally nothing that the compiler is supposed to do following
undefined behavior.

~~~
noselasd
Is the undefined behavior applicable at runtime or at compile time ?

i.e. Is it undefined behavior if there are code paths that might access an
array out of bounds, but at runtime it actually never would ?

~~~
PeterisP
That's the exact thing that the compiler is doing - if there are code paths
that might access an array out of bounds, then the compiler is assuming that
in runtime it actually never would happen.

With this assumption you might even deduce that

    
    
        int f(int x) {
          if (x != 42) undefined_behavior;
          return x;
        }

is the equivalent of

    
    
        int f(int x) {
          return 42;
        }

since (according to the assumption) in runtime it would always be called in a
way that doesn't reach the undefined behavior, i.e., with x=42. And, of
course, the many similar assumptions about array boundaries, pointer
nullabilities, numbers not reaching overflow, etc.

------
thedufer
Does the optimization performed on `unwitting` require the compiler to
determine that `ring_bell` will return (as opposed to calling `exit`) or is
there something in the spec that allows it to assume that functions return?

------
detrino
Here is a fun demonstration of undefined behaviour that someone showed me
recently: [http://ideone.com/LsEUPa](http://ideone.com/LsEUPa)

~~~
anon4
GCC seems helpful here

    
    
      $ g++ -O2 -Wall -Werror -o asdf asdf.cpp
      asdf.cpp: In function ‘int main(int, char**)’:
      asdf.cpp:9:29: error: iteration 3u invokes undefined behavior [-Werror=aggressive-loop-optimizations]
         std::cout << (i*1000000000) << std::endl;
                                   ^
      asdf.cpp:7:2: note: containing loop
        for (int i = 0; i < 4; ++i)
        ^
      cc1plus: all warnings being treated as errors
    

Clang compiles it but doesn't produce an endless loop.

You can get defined behaviour by casting to unsigned:

    
    
      std::cout << (int)(((unsigned) i) * 1000000000U) << std::endl;
    

Which seems like a good rule of thumb: when working on x86 and x64 and doing
things with numbers that you think might overflow, do it with unsigned and
cast back to what you need.

------
switch33
Please read this:
[http://www.reddit.com/r/practicalagi/comments/29452o/can_we_...](http://www.reddit.com/r/practicalagi/comments/29452o/can_we_teach_computers_higher_order_logic_is_it/)

------
EGreg
So according to this,

    
    
      if (b)
        a = *b;
      else
        a = 3;
    

Since dereferencing 0 is undefined, the compiler can assume that a = 3 never
needs to be executed??

But b may legitimately be 0 and then the second branch SHOULD be entered

How does this fit with what the author said? The compiler cant just go back
and assume b is never zero just because it's being dereferenced, since the
dereference is guarded.

That's why his last part doesnt make sense -- that even f you try to prevent a
bad dereference, undefined behavior is triggered.

~~~
mikeash
No, that's not true at all. You check before you dereference, which is
completely legitimate. If b is NULL then the dereference never happens,
exactly as it should be.

To invoke undefined behavior and strange optimizations, you'd need to
rearrange the code a bit:

    
    
        a = *b;
        if (!b)
            a = 3;
    

Here, the compiler can omit the if statement and its contents entirely,
because b cannot be NULL, because the first line would invoke undefined
behavior if it were.

A check for NULL before you dereference is always safe. It's when you do it
the other way around that the compiler can start doing strange things.

