4
votes

Looking at C11 6.3.2.1 paragraph 3:

Except when it is the operand of the sizeof operator, the _Alignof operator, or the unary & operator, or is a string literal used to initialize an array, an expression that has type "array of type" is converted to an expression with type "pointer to type" that points to the initial element of the array object and is not an lvalue. If the array object has register storage class, the behavior is undefined.

Undefined behaviour seems like an odd choice for this situation. Undefined behaviour "imposes no requirements" (3.4.3). In other words, according to only the wording of 6.3.2.1, indexing into (or doing a few other things with) an array declared with register is presumably permitted to compile, run and do exactly what the code looks like it does without issuing an error.

register int a[5];
a[0] = 6; // apparently not required to cause an error?

This seems to contradict the spirit of the keyword, which (per 6.5.3.2) prevents an lvalue's address being taken with &. This is not quite the same thing, but it's certainly related, as implicit array->pointer conversion, and & on an lvalue, generate the same kind of result: a pointer to the object's storage.

The footnote to 6.7.1 makes this relationship explicit:

the address of any part of an object declared with storage-class specifier register cannot be computed, either explicitly (by use of the unary & operator as discussed in 6.5.3.2) or implicitly (by converting an array name to a pointer as discussed in 6.3.2.1).

So if it "can't" be done, why is the conversion undefined instead of erroneous, or (for indexing, where there are a few other options) implementation-defined?

It doesn't read like an oversight in 6.3.2.1, since register's meaning is straightforward enough according to the other mentions; I'd assume it to be perfectly well-defined if that sentence didn't say otherwise. What is there to be in doubt about?

1
Related to Is it possible to keep an entire array in cpu register, I saw that question recently although I can't remember why.Shafik Yaghmour
I wonder if the intention was to allow for extensions, but not impose any requirements on their exact meaning? There are many situations where C suffers by not having a term whose meaning is less rigid than Implementation-defined (which requires implementations to specify something) or Unspecified (which requires selection from a fixed set of choices), but not so loose as Undefined (anything goes). Personally, I think register could be a useful keyword if a function which took a parameter of type register int* promised not to persist the pointer, and thus callers of such a function...supercat
...could keep variables whose address was passed to that function (but not generally exposed) could keep those variables cached in registers while calling other functions (which might, but for the register keyword, be able to access pointers retained by the earlier function calls).supercat

1 Answers

1
votes

Remember that Undefined Behavior allows everything, including "behaving in a way that's expected on the particular platform". I.e. for a platform that has hardware array registers, you'd want it to compile, for a platform which does not you don't want it to compile. Leaving it UB allows both.

IIRC a 6502 had 256 memory-mapped registers at the start of address space.