> I think it is a challenge for a compiler to know where the variable is declared.
Not really.
At the start of the .c file, the scope is global.
After that, it's just a matter of keeping track of how many open and close braces it sees.
Code:
/* Starts in global scope */
int global;
void foo ( void )
{ /* now a local scope begins */

}
/* back to a global scope */
Example, using objdump to display detailed information about each symbol.
Code:
$ cat foo.c
#include <stdio.h>
extern int other;
int gvar = 2;
static int lvar = 22;
int main ( ) {
    int temp = 42;
    printf("%d %d %d %d\n", other, gvar, lvar, temp);
}
$ gcc -c foo.c
$ objdump -t foo.o

foo.o:     file format elf64-x86-64

SYMBOL TABLE:
0000000000000000 l    df *ABS*	0000000000000000 foo.c
0000000000000000 l    d  .text	0000000000000000 .text
0000000000000000 l    d  .data	0000000000000000 .data
0000000000000000 l    d  .bss	0000000000000000 .bss
0000000000000004 l     O .data	0000000000000004 lvar
0000000000000000 l    d  .rodata	0000000000000000 .rodata
0000000000000000 l    d  .note.GNU-stack	0000000000000000 .note.GNU-stack
0000000000000000 l    d  .note.gnu.property	0000000000000000 .note.gnu.property
0000000000000000 l    d  .eh_frame	0000000000000000 .eh_frame
0000000000000000 l    d  .comment	0000000000000000 .comment
0000000000000000 g     O .data	0000000000000004 gvar
0000000000000000 g     F .text	0000000000000045 main
0000000000000000         *UND*	0000000000000000 other
0000000000000000         *UND*	0000000000000000 _GLOBAL_OFFSET_TABLE_
0000000000000000         *UND*	0000000000000000 printf