Death of the register keyword may be greatly exaggerated

The register keyword is often swept to the sidelines in C and C++ courses, books, and articles, because it’s considered by many these days to be obsolete. While there are reasons for this belief, reports of the death of the register keyword are still a bit premature. I admit that I have been guilty of this myself, in the interest of time, especially when teaching introductory courses on C or C++.

To register or not to register

In the C programming language, the register keyword (a storage-class specifier in the standards documents) was originally designed to allow the developer to inform the compiler that a particular local variable or function parameter will be used frequently, and that the compiler should keep that variable in a CPU register if possible, minimizing slower round trips to memory. The keyword has always been a hint or suggestion to the compiler, and the compiler is under no obligation to actually take the hint. The C++ language picked up the register keyword as a C-compatible feature.

When compiler optimizers were young and innocent and not always that great at optimizing CPU register usage, a developer could use register to nudge the compiler in the right optimization direction, assuming the developer knew best. But that assumption is not always a good one. If the developer chooses unwisely, requesting that an infrequently-used variable occupy a precious CPU register, and the compiler might dutifully generate code to do so, and the negative effect on performance can be huge.

As mainstream compilers for both C and C++ have matured, and optimizers have become much more sophisticated, they’ve gotten better at generating code that optimizes the use of registers — so much better, in fact, that they typically do a better job than the developer. Nowadays, many modern mainstream compilers simply ignore the register hint completely, and take their own approaches to register optimization.

Beyond the potential positive or negative effects on performance, the only limitation on using a register variable in C is that you can’t take its address. In most architectures, registers don’t typically have memory addresses, so this wouldn’t make sense anyway. In C++, you can take the address of a register variable, but doing so puts the variable in memory and not in a register, which defeats the purpose of using the register storage class, if your compiler pays attention to it.

The register keyword in modern C

The C11 standard explains the register keyword this way:

A declaration of an identifier for an object with storage-class specifier register suggests that access to the object be as fast as possible. The extent to which such suggestions are effective is implementation-defined.

[Footnote 121] The implementation may treat any register declaration simply as an auto declaration. However, whether or not addressable storage is actually used, 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…) or implicitly (by converting an array name to a pointer…). Thus, the only operator that can be applied to an array declared with storage-class specifier register is sizeof.

But if you look in a mainstream C compiler’s documentation, you’re likely to find something like this:

The compiler does not accept user requests for register variables; instead, it makes its own register choices when global register-allocation optimization is on. However, all other semantics associated with the register keyword are honored.

You might have noticed that I used the qualified term “mainstream C compilers.” There are many compilers out there that still pay attention to the register hint, especially in the embedded systems world where a compiler targeted at a specific microcontroller architecture:

  • Might be relatively immature, and might not yet have a strong optimizer in place.
  • Might be relatively mature, and might not have had its optimizer revised in a very long time.
  • Might not have an optimizer at all, to cut down on compiler development costs.
  • Might offer a low-cost version with no optimizer, and a higher-cost version with an optimizer.

It’s unlikely that the register keyword will be deprecated or removed from the C language standard any time soon, given that so many compiler implementations for embedded systems still heed the register hint. But in mainstream C compilers with powerful optimizers, you’ll likely see that the implementation ignores the hint altogether.

Note: The gcc compiler has a non-standard extension that uses the register keyword to assign variables to specific named registers when using inline assembly language. The usage is documented here. But you need to be extremely careful with this, because it’s very easy to inadvertently clobber the registers. And, of course, any code that uses this non-standard extension will be non-portable.

The register keyword in modern C++

The register keyword has been on its way out of the C++ language for several years.  The rationale behind this is expressed in the C++ Standard Core Language Defect Reports and Accepted Issues in 2009:

The register keyword serves very little function, offering no more than a hint that a note says is typically ignored. It should be deprecated in this version of the standard, freeing the reserved name up for use in a future standard…

And, as a result, here’s what the C++11 standard said about register:

The register specifier shall be applied only to names of variables declared in a block or to function
parameters. It specifies that the named variable has automatic storage duration. A variable
declared without a storage-class-specifier at block scope or declared as a function parameter has automatic
storage duration by default.

A register specifier is a hint to the implementation that the variable so declared will be heavily used.
[ Note: The hint can be ignored and in most implementations it will be ignored if the address of the variable
is taken. This use is deprecated. —end note ]

and later in Annex D Compatibility Features, the C++11 standard states:

The use of the register keyword as a storage-class-specifier is deprecated.

So, as of the 2011 C++ standard, use of the register keyword was still tolerated and supported, but not recommended. It remained in place to allow existing code to compile properly, but was essentially not recommended for new code.

Three years later, in the C++14 standard, the register keyword remained a storage class specifier, and remained deprecated.

Fast-forward three more years. In the C++17 standard, the register keyword still appears in the official list of C++ keywords, but it is followed by a note:

The export and register keywords are unused but are reserved for future use.

Then, in Annex C Compatibility, the C++17 standard states:

Change: In C++, register is not a storage class specifier.
Rationale: The storage class specifier had no effect in C++.
Effect on original feature: Deletion of semantically well-defined feature.
Difficulty of converting: Syntactic transformation.
How widely used: Common.

In practice, there are still some C++ compilers for microcontrollers that pay attention to the register hint. But the vast majority of C++ compilers ignore the hint.

To be C++17 compliant, a C++ compiler implementation will now need to flag the use of register as a storage-class specifier as an error, and will need to continue to treat the register keyword as reserved. As a result, some compilers may never move forward to become fully C++17 compliant.

Is it dead yet?

In C, the register keyword is far from dead.  It still has meaning as of the C11 standard, although most mainstream compilers ignore it. In the embedded world, in specific implementations, the register keyword lives.

In C++, in terms of the C++17 standard, although the register keyword no longer has a specific meaning in the language, the keyword is still there and is reserved for future use. What the register keyword might be used for in the future is anybody’s guess. It might just remain in a perpetual state of “reserved for future use,” not truly dead and not truly alive.