MrPaulch
MrPaulch

Reputation: 1418

Does undefined behaviour have to be defined to be undefined?

Despite the appearance of the title, this is not a philosophical question.

It is however strictly about semantics.

Background

This answer, in an earlier edit, stated that reading uninitialized values is undefined behavior. I was in agreement with that assessment. Others were not.

After some digging I came to the conclusion that it is undefined whether it is undefined behaviour.

The subsection 2 in 6.3.2.1 of the standard only states that accessing an uninitialized object that

[...] could have been declared with the register storage class [...]

is undefined behaviour.

Other sections, such as 6.7.9 [Initializers] or 6.5.2.1 [Array subscripting] don't mention accessing uninitialized values.

So there is no question about whether there is a mention that this particular case is called undefined behavior by the standard.

Semantics

However: 3.4.3 1 states that undefined behaviour is

behavior, upon use of a nonportable or erroneous program construct or of erroneous data, for which this International Standard imposes no requirements

Reading from an uninitialized array

I would call that "undefined behavior". But maybe I'm missing something(?)

And the broader question remains:

Does a behavior have to be explicitly mentioned as such, to be considered "undefined behavior"?

Notes:

Footnotes:

  1. malloc calloc and such, do have requirements on how the allocation is performed. But declaring a fixed length array does not specify the nature of the uninitialized data.

Upvotes: 0

Views: 113

Answers (3)

supercat
supercat

Reputation: 81189

The C89 Standard was written primarily from a descriptive rather than a prescriptive standpoint, at a time when many actions would have behaviors specified by some but not all implementations, based in significant measure upon the target platforms and application fields. Further, many actions would have predictable and easily-specifiable consequences in some circumstances but not others. The authors of the Standard made no effort to exhaustively describe all combinations of circumstances when quality implementations should be expected to behave predictably, and even go so far as to note in their rationale document that a sufficiently poor-quality implementation could be simultaneously conforming and useless.

With reference to reading uninitialized data from an array, consider the following code:

float test(int a, int b, int c, int d)
{
  float result;
  float *f = malloc(10*sizeof float);
  f[a] = 1.0f; f[b] = 2.0f; f[c] = 3.0f;
  result = f[d];
  free(f);
  return result;
}

On some platforms, attempting to process certain bit patterns as floating-point numbers might trigger a possibly-non-configured trap handler. I think it's pretty clear that the authors of the Standard wanted to avoid requiring that implementations for such platforms prevent code like the above from firing the trap handler if e.g. code calls `test(1,2,3,4), nor requiring that they limit the consequences of such a trap if it fires. Further, I don't think they would have viewed negatively the quality implementations on such platforms where such code might yield arbitrary and unpredictable effects.

Suppose, however, the code had been:

typedef struct { float v; } flt;

flt test2(int a, int b, int c, int d)
{
  flt result;
  flt *f = malloc(10*sizeof flt);
  f[a].v = 1.0f; f[b].v = 2.0f; f[c].v = 3.0f;
  result = f[d];
  free(f);
  return result;
}

The Standard explicitly forbids structures from having trap representations. As a consequence, there would be no way that f[d] could hold a trap representation, and thus no reason for anything weird to happen when it is read. If code which calls test2 were to attempt to use field v of the result, that might trigger an unwanted trap handler, but if nothing ever does anything with that field there should be no problem.

Unfortunately, because the authors of the Standard were not trying to list out all the combinations of circumstances where quality implementations should be expected to behave predictably, they didn't think it necessary to distinguish between cases where certain actions might have unpredictable actions in some circumstances, from those where the actions should be viewed as never having predictable consequences even in cases where other parts of the Standard or an implementation's documentation would otherwise guarantee the behavior.

The authors of C89 stated that they wanted to avoid breaking existing code whenever possible, but they failed to explicitly specify behavior in many combinations of circumstances where programs would rely upon them. Unless they were lying or incompetent, such failure would most logically be attributed to a belief that implementers shouldn't need a complete list of all such behaviors in order to recognize and support them where required.

Should one be able to trust that a compiler which is configured to behave as a quality implementation suitable for low-level programming, which targets a platform that doesn't have trap representations, won't totally jump the rails when reading an uninitialized array value? Given the low cost of supporting such a guarantee, I would suggest that any implementation that can't uphold it isn't a quality compiler suitable for low-level programming.

Should one trust that gcc and clang won't behave oddly when given code which relies upon behaviors not mandated by the Standard, or even does things which are defined by the Standard but would, in the absence of optimization be equivalent to ones that aren't? No. It really doesn't matter whether the Standard defines a particular behavior if some compilers process the behaviors usefully and others try to avoid doing so and view the few cases where the Standard tries to explicitly defines the behavior as defects in the Standard.

Upvotes: 1

Jens Gustedt
Jens Gustedt

Reputation: 78923

No, behavior is undefined when it is not defined, forbidden "shall not" or explicitly marked as undefined. Here is the definition of these things in the C standard, clause 4 "Conformance", para 2:

If a “shall” or “shall not” requirement that appears outside of a constraint or runtime-constraint is violated, the behavior is undefined. Undefined behavior is otherwise indicated in this International Standard by the words “undefined behavior” or by the omission of any explicit definition of behavior. There is no difference in emphasis among these three; they all describe “behavior that is undefined”.

Upvotes: 2

haccks
haccks

Reputation: 106052

When an object is not initialized then it has indeterminate* value. Accessing this object invoke undefined behavior only if this value is a trap representations.
That says the answer you are referring to has wrong reasoning. Accepted answer is the correct one.


* indeterminate value:
either an unspecified value or a trap representation
unspecified value:
valid value of the relevant type where this International Standard imposes no requirements on which value is chosen in any instance
NOTE An unspecified value cannot be a trap representation.
trap representation:
an object representation that need not represent a value of the object type
(§3.19.2, §3.19.3, §3.19.4)

Upvotes: 1

Related Questions