Author: Wylie Garvin
Date: 15:01:05 11/27/01
Go up one level in this thread
On November 27, 2001 at 16:12:24, Miguel A. Ballicora wrote:
>Has anybody experienced bugs from MS Visual C 6.0 when programming in C?
>A couple of times I have observed weird behaviors that after a lot of debugging
>I had to conclude that there was something weird with the compiler or optimizer.
> [....]
My experience has almost always been that I was wrong and the compiler was
right. I think I remember encountering one (one!) genuine bug in MS's C
compiler, but it was years ago.
So what else could it be besides an optimizer bug? There are four reasons I can
think of why a program might not behave as expected:
(1) The program is "valid" (in the sense that it obeys the C language
specification) but it does contain a bug. This is the most common case, and it
can usually be solved by debugging.
(2) You are using "unsafe" optimizations; these optimizations might make
assumptions about your program that are stronger than the guarantees provided by
the language. Which is fine unless the assumptions turn out to be false for
your particular program! An example is the /Oa option of MSVC and compatible
compilers--it makes the compiler assume that you never access the same storage
through both a global pointer and a local (auto) pointer variable in the same
function. This is pretty rare, but there are cases where you might do it and if
you did the optimized binary could behave incorrectly.
It's important to remember that if the bug goes away when you compile with
optimizations turned off, that does NOT mean the bug is an optimizer bug (or
even that it results from an unsafe optimization, as above). The erroneous
condition probably still exists in the unoptimized version; it just doesn't have
harmful effects. E.g. an off-by-one error causing you to overwrite the int
immediately after an array of ints might not be harmful in the unoptimized
binary because the space after the array is unused, but when you optimize the
compiler lays out the stack frame more intelligently and as a result the slot
that gets trashed has something useful in it.
(3) Your program invokes IMPLEMENTATION-DEFINED behaviour. It program relies on
a particular case for which the C language says the compiler should do something
sensible, but doesn't specify what. The compiler writer has to choose a
sensible behaviour and document it (this is typically stuff like the signedness
of "char" or bitfields, internal compiler limits, etc). You should always be
able to rely on such behaviour as long as you never switch or upgrade your
compiler. But your program might produce "incorrect" results when compiled with
one compiler, because you have written it according to the
implementation-defined behaviours of a different compiler. This sometimes
happens when people learn C with a particular compiler and don't realize that
some of its behaviours are implementation-defined. Since it is non-portable,
it's usually best to just avoid relying on implementation-defined behaviour.
(4) Your program invokes UNDEFINED behaviour. This is a weird one; in this case
the compiler may do whatever the hell it wants:
Undefined behaviour occurs when an erroneous program construct or
bad data is used, and the standard does not impose a behaviour.
Possible actions of undefined behaviour include ignoring the problem,
behaving in a documented manner, terminating the compilation with an
error, and terminating the execution with an error.
In other words, anything (a compiler's "documented manner" of behaving might be
to go into an infinite loop, or to produce an executable that prints "Hello
World"). Most compilers do not notice most types of undefined behaviour, so
they will TRY to compile your program as if it were a correct program. But if
the program doesn't do what you want, you can't blame the compiler because *by
definition* whatever the compiler does is "correct". Undefined behaviour is
very bad and should always be avoided.
An example of UNDEFINED behaviour would be, using a pointer to iterate over an
array and allowing the pointer to (1) point to any address less than the first
element of the array, or (2) point to any address after the last element of the
array plus one. Okay, okay, so everyone does it and it always works, right?
But the language says not to do it, so you should never do it. It's like
initializing arrays of pointers with memset instead of by assignment. It always
works (binary zero is the null pointer on all modern machines), but the language
doesn't say that this will always be the case (note: I am going from an old
version of the spec; they may have changed this).
OTOH, it is practically impossible to prove that the behaviour of an
optimizing compiler is correct under all possible conditions. Sometimes the
optimizer really is broken. It's pretty rare, but it can happen.
good luck,
Wylie
This page took 0 seconds to execute
Last modified: Thu, 15 Apr 21 08:11:13 -0700
Current Computer Chess Club Forums at Talkchess. This site by Sean Mintz.