Search code examples
ccompiler-constructioncompiler-optimizationcpu-architecturecpu-registers

Reasons a C Compiler Ignores register Declaration


What are the reasons behind a C compiler ignoring register declarations? I understand that this declaration is essentially meaningless for modern compilers since they store values in registers when appropriate. I'm taking a Computer Architecture class so it's important that I understand why this is the case for older compilers.

"A register declaration advises the compiler that the variable in question will be heavily used. The idea is that register variables are to be placed in machine registers, which may result in smaller and faster programs. But compilers are free to ignore the advice." ~ The C Programming Language by Brian W. Kernighan and Dennis M. Ritchie

Thank you!


Solution

  • Historical C compilers might only be "smart" (complex) enough to look at one C statement at a time, like modern TinyCC. Or parse a whole function to find out how many variables there are, then come back and still only do code-gen one statement at a time. For examples of how simplistic and naive old compilers are, see Why do C to Z80 compilers produce poor code? - some of the examples shown could have been optimized for the special simple case, but weren't. (Despite Z80 and 6502 being quite poor C compiler targets because (unlike PDP-11) "a pointer" isn't something you can just keep in a register.)

    With optimization enabled, modern compilers do have enough RAM (and compile-time) available to use more complex algorithms to map out register allocation for the whole function and make good decisions anyway, after inlining. (e.g. transform the program logic into an SSA form.) See also https://en.wikipedia.org/wiki/Register_allocation

    The register keyword becomes pointless; the compiler can already notice when the address of a variable isn't taken (something the register keyword disallows). Or when it can optimize away the address-taking and keep a variable in a register anyway.

    TL:DR: Modern compilers no longer need hand-holding to fully apply the as-if rule in the ways the register keyword hinted at.

    They basically always keep everything in registers except when forced to spill it back to memory, unless you disable optimization for fully consistent debugging. (So you can change variables with a debugger when stopped at a breakpoint, or even jump between statements.)


    Fun fact: Why does clang produce inefficient asm with -O0 (for this simple floating point sum)? shows an example where register float makes modern GCC and clang create more efficient asm with optimization disabled.