GCC 4.8 Breaks Broken SPEC 2006 Benchmarks (fixed)
lwn.net
lwn.net
int array[16];
int *ptr = array + 16;
But it's not legal to actually dereference this pointer.The distinction is confusing to a lot of programmers, used to modern architectures where pointers and integers share registers and you can do crazy things with your pointer variables with no consequences. However, some architectures will crash just for having a bad pointer value. That is, this is not legal:
int array[16];
int *ptr = array + 17;
Even if you never dereference ptr, this code is still illegal, and the compiler is within its rights to generate code that crashes or makes demons fly out of your nose when it encounters this source. The first case is an exception made to make it easier to iterate over an array. So there's a spot in the C standard that says, essentially, "It is OK to have a pointer that points one element past the end of an array." This is probably not too hard to misread as "It is OK to read one element past the end of an array." Although that is definitely wrong.I bring up VM behavior because, particularly given the timeframe, it's important to consider that there once was a time when VM did not exist, and SEGV meant an actual _segmentation_ fault.
As for VM and not-VM, I don't really see why it matters. It would be perfectly valid for a C compiler to generate code that does bounds checking on every array access, for example. And there were far weirder ways to do memory hardware than the standard MMU with 4kB pages that we have today, and the legacy of that can be found throughout the C standard. For example, the fact that it's illegal to store many values in a pointer, not dereference but merely store, comes from systems that really would check that stuff and crash your app if you had a bad pointer value in the wrong place, even if you didn't try to load what it pointed to.
If it were not valid C code, then a standard-compliant C compiler would be required to reject it.
Now don't get me wrong, having code that is dependent dereferencing past the end of an array is not a good idea. Having such code in a popular benchmark is a bad situation to be in, particularly when it interferes with progress in compiler optimization. It is not, however, "illegal" for any standard definition of illegal, and in this context clarity is important as ambiguity can interfere with compiler optimization :)
Edit: Assume you have a machine/OS with a page size of 1 byte. Can a compiler generate a program that does not raise an exception in the situation mentioned by the OP? Saying such a program is illegal would surely imply that it could not.
Nope! Here's a direct quote out of the copy of the standard I have lying about, from the section that discusses when diagnostic output is required:
"The intent is that an implementation should identify the nature of, and where possible localize, each violation. Of course, an implementation is free to produce any number of diagnostics as long as a valid program is still correctly translated. It may also successfully translate an invalid program."
That last sentence is particularly pertinent.
Many (most?) languages have two kinds of programs: valid and invalid. C has three: valid, invalid requiring the compiler to reject it, and invalid but the compiler may still accept it. For whatever reason, people have a very hard time understanding the implications of the third category, or, as in your case, even realizing that it exists.
> A conforming implementation shall produce at least one diagnostic message (identified in an implementation-defined manner) if a preprocessing translation unit or translation unit contains a violation of any syntax rule or constraint, even if the behavior is also explicitly specified as undefined or implementation-defined.
would seem to imply that a standard compliant compiler would need to produce a message for an invalid program. I will say it is both interesting and surprising (to me) that at least C99 does allow for the successful translation of an invalid C program.
Regardless, while informative, I'll probably stop posting on this topic before I lose the ability to ;)
- C makes it undefined to access beyond the end of an array. It's possible to define this as always throwing an exception or terminating the program.
- C makes it undefined to overflow a signed integer type. We can define this as following two's complement rules, or terminating the program.
- Writing to a constant string is undefined. We can define this to always work, to always terminate the program, or we can even define a type system that makes it impossible to even try.
Once you define all behavior like this, then there is no undefined behavior. A valid program is one that your compiler accepts, no more and no less. The program can still have bugs, of course, but if the program compiles, its output is always well-defined.
Potayto, potahto. Violating language semantics results in undefined behaviour. You can only get less legal if you add syntax errors into the mix.
You could make the argument that some undefined behaviour should only be considered nonportable instead of erroneous, but personally I consider code that triggers undefined behaviour as invalid or possibly written in a non-standard C dialect.
Why not something like this:
int d[16];
int SATD (void)
{
int satd = 0, dd, k;
for (k=0; k<16; ++k) {
dd = d[k];
satd += (dd < 0 ? -dd : dd);
}
return satd;
}
Does this generate slower code?No, or at least not with an optimizing compiler. It was probably more about writing compact code.
Personally, I'd have used abs() and gotten rid of the temporary variable dd altogether:
int SATD (void)
{
int satd = 0, k;
for (k = 0; k < sizeof d / sizeof *d; ++k)
satd += abs(d[k]);
return satd;
}A C compiler from the 1970's was almost little more than a glorified pattern matcher. It easily could emit different code for "i += 1" vs "++i" vs "i++" (and that would not be a problem, as the C programmers used C as a replacement for assembly, and there, "INC R" is different from "ADD #1,R")
To check whether that theory holds water, one would have to check older SPEC benchmarks, to see where this fragments came from.
The bug is in version 9.3 used by SPEC CPU2006, but no longer in version 9.5.
The current version is 18.4...
Or does it take a law to make them fix bugs in the code they ship?
As long as a warning is emitted when the compiler relies only such undefined behavior it is a good thing to optimize this heavily, there are not many other ways to get more performance from C/C++.