2

In the context of writing a C compiler, what is the usefulness of the 'register' keyword? I recently came across a statement that said the 'register' keyword can be useful to compiler writers when making entries in a symbol table. However, I have not been able to find any concrete examples or explanations regarding this particular usage.

I understand that the 'register' keyword hints to the compiler that a variable should be stored in a processor register for optimized access. However, in modern compilers, it is typically the compiler's responsibility to determine the best allocation of variables in registers, rendering the 'register' keyword obsolete for most programmers.

My specific inquiry is focused on understanding any potential scenarios or code examples where the 'register' keyword could be justified or useful in the context of writing a C compiler. I'm interested in finding any historical sources or insights that align with the mentioned statements, shedding light on the practical utility of 'register' in compiler development.

Please note that I'm solely interested in understanding the possible relevance of the 'register' keyword when programming a compiler in C, rather than using it as a regular C programmer.

zoldxk
  • 2,632
  • 1
  • 7
  • 29
  • Reference please. – Ian Abbott Jul 14 '23 at 15:28
  • 2
    @zoldxk, `register` also has the side effect of disallowing use of `&` with a `register` object - be it in a register or not. – chux - Reinstate Monica Jul 14 '23 at 15:28
  • Perhaps it is similar to the statement about the `auto` keyword being "mostly meaningful to a compiler-writer making an entry in a symbol table" from *"Expert C Programming - Deep C Secrets"* mentioned in https://stackoverflow.com/q/76527163/5264491 – Ian Abbott Jul 14 '23 at 15:36

2 Answers2

4

The intended use of the register storage class is to promote optimization.

From the C23 draft 6.7.1 Storage-class specifiers

  1. 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.
    1. The implementation can treat any register declaration simply as an auto declaration. However, whether or not addressable storage is 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 as discussed in 6.5.3.2) or implicitly (by converting an array name to a pointer as discussed in 6.3.2.1). Thus, the only operator that can be applied to an array declared with storage-class specifier register is sizeof and the typeof operators.

It comes with restrictions, such as that you may not take the address of a variable declared with this storage-class, but other than that, you are free to treat it as a regular automatic variable declaration in your compiler.

Since it's only a hint to the compiler, an implementation can therefore disregard register and optimize the code in a different way if it finds that likely to be faster. In current implementations, you'll therefore likely not see any difference when using register unless you look at an unoptimized build.

Demo

Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
  • 2
    Indeed, `register` does nothing (except disallow `&` to take the address) if optimization is enabled in GCC, clang, and (all?) other modern compilers. They don't need the help and disregard it. But if you disable optimization (GCC/clang's default of `-O0`), then non-`register` variables have a memory address and aren't kept in registers across statements: [Why does clang produce inefficient asm with -O0 (for this simple floating point sum)?](https://stackoverflow.com/q/53366394) has an example. – Peter Cordes Jul 14 '23 at 16:56
  • @PeterCordes Thanks! Great answer to that question there! – Ted Lyngmo Jul 14 '23 at 17:01
4

As far as I know, there are only two properties of register that are mandatory according to the C standard: you can't take the address, and you can't use it with _Alignas. So your compiler must issue a diagnostic for code such as the following:

register int foo;
&foo; // constraint violation

register int bar[3]; // basically useless but not an error by itself
bar; // constraint violation, decays to pointer
bar[1]; // likewise

register _Alignas(16) int baz; // constraint violation

(Incidentally, gcc actually allows register int bar[3]; bar[1]; without a diagnostic; but it doesn't claim to be a conforming C implementation unless you use -pedantic, and in that case you do get a warning as you should.)

Note that a "diagnostic" need not be an error; a warning suffices. So for the above code snippets, if you want, you can issue a warning and then proceed to handle them in any way that you wish (e.g. by otherwise ignoring the register declaration).

And of course, since it is a keyword, you must issue a diagnostic if it appears where not syntactically allowed, e.g. as an identifier.

int register;
void register(void);

Everything else is at your discretion as the implementer. You can ignore it completely if you choose, and for the most part, that's what modern compilers do.

The standard's intended meaning for register is to "suggest that access to the object be as fast as possible." Traditionally, this might be used in a register allocation pass. If you are in a situation where you have run out of machine registers and need to spill some variables into memory, then you might give priority to register variables and try to spill non-register variables first. For instance, if you have

int a,b,c,d,e,f,g,h;
register int r;

then you might prefer to spill any or all of a,...,h instead of r, if possible.

That would be reasonable for a compiler of the 1970s or 1980s. On the other hand, modern optimizing compilers will have sophisticated heuristics to decide how to allocate registers, that in many cases are more accurate than the programmer's assumptions. So a modern compiler would be more likely to rely entirely on its own algorithm and disregard the register hint.

The "entry in a symbol table" remark might be from the same source (Expert C Programming - Deep C Secrets) as mentioned in Why is the 'auto' keyword useful for compiler writers in C?. It is not very clearly worded. The only sense I can make of it is that the compiler does need to keep track of which storage class specifiers were used to declare an object, e.g. to issue required diagnostics as noted above. So whatever data structure you use to associate the name and type of an object will need to have a flag to indicate if it was declared register, and that may be what they mean by "symbol table".

Nate Eldredge
  • 48,811
  • 6
  • 54
  • 82