How does the compiler differentiate indentically-named items

Viewed 131

In the following example:

int main(void) {
    int a=7;
    {
        int a=8;
    }
}

The generated assembly would be something like this (from Compiler Explorer) without optimizations:

main:
        pushq   %rbp
        movq    %rsp, %rbp
        movl    $7, -4(%rbp)   // outer scope: int a=7
        movl    $8, -8(%rbp)   // inner scope: int a=8 
        movl    $0, %eax
        popq    %rbp
        ret

How does the compiler know where the variable is if there are duplicately-named variables? That is, when in the inner scope, the memory address is at %rbp-8 and when in the outer scope the address is at %rbp-4.

2 Answers

There are many ways to implement the local scoping rule. Here is a simple example:

  • the compiler can keep a list of nested scopes, each with its own list of symbol definitions.
  • this list initially has a single element for the global scope,
  • when it parses a function definition, it adds a new scope element in front of the scope list for the function argument names, and adds each argument name with the corresponding information in the identifier list of this scope element.
  • for each new block, it adds a new scope element in front of the scope list. for ( introduces a new scope too for definitions in its first clause.
  • upon leaving the scope (at the end of the block), it pops the scope element from the scope list.
  • when it parses a declaration or a definition, if the corresponding symbol is already in the current scope's list, it is a local redefinition, which is forbidden (except for extern forward declarations). Otherwise the symbol is added to the scope list.
  • when it encounters a symbol in an expression, it looks it up in the current scope's list of symbols, and each successive scope in the scope list until it finds it. If the symbol cannot be found, it is undefined, which is an error according to the latest C Standard. Otherwise the symbol information is used for further parsing and code generation.

The above steps are performed for type and object names, a separate list of symbols is maintained for struct, union and enum tags.

Preprocessing is performed before all of this occurs, in a separate phase of program translation.

The C programming language has some specification, like n1570 (or newer ones). That specification defines in §6.2.1 the scope of an identifier.

So any C compiler should follow that specification.

How does a C compiler implements that specification requires a good book for explanations. I recommend the Dragon book.

Some simple or complex C compilers are open source. Look inside the source code of TinyCC, nwcc, Clang, or GCC to understand how they implement that specification (they have symbol tables, but details are specific to each compiler).

How does the compiler know where the variable is if there are duplicately-named variables?

It manages symbol tables, and update them when parsing blocks. Usually, a compiler build some abstract syntax tree of the compiled source code, and leafs in that tree representing variables refer to some symbol table. The GCC compiler documents its Generic Tree and GIMPLE data structures and provide dump options to output them. You could also compile your foo.c as gcc -S -O -fverbose-asm foo.c and look into the emitted assembler code foo.s.


At last, your example can be considered as poor programming style. Some coding guidelines (like MISRA-C or GNU coding standards) disallow or discourage it. Your code review process should catch such code (in my opinion, your example is a quite unreadable code).

My feeling is that single letter variables should have a very small scope - a dozen of lines at most.

I suggest to look (for inspiration) inside the C code of existing free software projects (like GNU bash or GNU make). Care has been taken to choose understandable names.

Take advantage of modern source code editors like GNU emacs or vim. You can configure them to type long identifiers with a few keyboard presses (they have auto-completion; and some input libraries like GNU readline provides that too). Since you (or your colleagues) will spend much more time in reading source code than in typing it, such an effort (naming well your variables and identifiers) is worth your valuable time.

If you use GCC as your compiler, invoke it as gcc -Wall -Wextra -g to get a lot of warnings and debug information. You could also use static source code analysis tools like Frama-C or the Clang static analyzer.

For real life software projects (for example GTK), you'll have a document specifying coding conventions, and you could write some GCC plugin checking most of them. See also the DECODER project.

For some parts of your software project, you may use C code generators like SWIG or GNU bison. In some cases, you would have your own C code generator. Then be sure to generate long C identifiers to reduce the possibility of name clashes.

Some code obfuscation tools are renaming most C identifiers. If you ship C source code without comments and with most identifiers generated like _0TwK4TkhEG the resulting C code can be compiled at your client site and would practically stay unreadable. You technically could write a code obfuscator transforming readable C code to cryptic C code.

Related