Optimisations and undefined behaviour

David Brown david.brown@hesbynett.no
Tue Nov 10 09:20:00 GMT 2015

On 09/11/15 17:31, Andrew Haley wrote:
> On 11/09/2015 03:56 PM, David Brown wrote:
>> We typically cannot use "sanatize" options, nor can we accept that a
>> bug in one part of the program causes undue and unnecessarily
>> damaging side-effects in other parts.
> Well, you have to get used to that.  It is reality: computers work
> that way.  I'm sure you know that if you hit the wrong I/O port
> with a wild write odd things will happen.  Whether that's "undue" or
> "unnecessary" I couldn't say: it just is.

There is no doubt that with C, it is easy to make mistakes that can lead
to disaster.  One little typo, and you access an array outside of its
bounds - with the result of corrupting a completely independent piece of
data, showing up as a problem in a different part of the program that
was already tested and working fine.  These things are part of the risk
of C programming - it's the cost you pay for maximum speed and efficiency.

But let's look back at Annex L in the C11 standards, and ask what it is
/for/.  It is labelled "Analyzability" - the aim is to help developers
(and code reviewers, and static analysers) figure out if code is correct
or not.  Part of that is to place bounds on the effects of some types of
undefined behaviour.  Now, you can certainly argue that Annex L doesn't
go far enough to be useful, or it is too vague, or that it is
impractical or limiting to implement, or that knock-on effects can
quickly turn bounded undefined behaviour into unbounded undefined
behaviour.  But the very existence of this Annex in the C standards
shows how important these things are.

To my mind, if execution hits UB, then it's a bug in the program.  It
doesn't really matter if the bug is UB, or an error in the algorithm, or
a coding mistake, or even a mistake in the customer's requirement
specifications.  It's a point where the computer does not do what the
user expects it to do.  Is it so unreasonable to want code that is
correct up to that bug, to run correctly until the bug is hit?  Is it
unreasonable to want the compiler to warn if it sees such a bug, and
uses it to change the past action of the code?

I know that once code hits the bug, all sorts of things can go wrong.
All I ask is that the compiler should not make things worse - at least
not without informing the developer.

> C definitely works that way.  Maybe there should be a nice small
> language which is useful for embedded developers and doesn't have
> all the interesting UB properties that C has.  (Ada, maybe?  Probably
> not.)  Maybe you could define a language compatible with C with the UB
> removed.  But defining the semantics of such a language would not be
> easy.  And I don't think it makes much sense to change GCC without
> such a rigorous language definition.

I don't believe it is possible to make a general and efficient
programming language without UB.  And even if it could be eliminated at
the language level, it would still exist at a higher level - a square
root function could well have undefined behaviour for negative inputs.
I also am happy that gcc can make many small optimisations based on its
knowledge of UB - strength reduction and constant folding in integer
expressions is a key example.

But I can also see the potential for optimisations based on UB to make
developers' lives much more difficult by working logically backwards in

(Note that in all my testing, I have not found gcc to perform any of
these unwanted "optimisations" that I fear - but I haven't found good
reason to be sure that they won't be performed in the future.)

And yes, I realise that taken to extremes, this is ultimately an
impossible task.  The point is merely to aim in that direction.  I don't
expect gcc to warn on every possible error - but I am happy to see it
warning on more and more possible errors.  What I don't want to see is
is that since it is impossible to know /exactly/ what the programmer
wanted when there is UB in the code, that means the compiler can refuse
all responsibility for trying.  That would IMHO be a disservice to the
user, making it harder to find and fix bugs.

More information about the Gcc-help mailing list