The function of the compiler is to convert each .c file into an 'object' file. To do this, it considers each .c file in relative isolation.
When main.c and main1.c are compiled, the compiler focuses on just one of them at a time.
When the compiler is focused on compiling main.c, main1.c and its content are not considered in any way.
The same goes for when the compiler is focused on compiling main1.c; main.c and its contents are not considered in any way.
Hence, the two .c files are compiled in complete isolation (from each other).
The resulting 'object' files contain machine code, an exported symbols list, an 'unresolved symbols' list, along with other things.
The function of a linker is to package all the 'object' files that make up an executable, and resolve the 'unresolved symbols' listed in each 'object' file.
It is the linker that makes the connection, for example, between the function 'foo()' exported by the 'main1' object file, and the 'unresolved external' 'foo()' required by the 'main' object file.
To be more correct, the line in main.c:
void foo(void);
Should probably be changed to:
extern void foo(void);
Since 'main.c' is compiled in isolation from 'main1.c', and since 'main.c' has no reference to 'a', the compiler reports an error.
Why can function defined in a different file access variables defined in that file without extern?
The variable 'a' is internal to the main1 object; and therefore is fully accessable to references in main1.
In fact, it is 'main' that cannot access 'a'. withhout some sort of 'extern int a' reference to inform the compiler of the characteristics of 'a', and that it will be the responsibility of the linker to resolve the actual location of 'a'.
Why does foo() print 12 even if variable 'a' is not defined in main.c?
The variable 'a' has no relationship to 'main.c'. It 'lives in' main1.c only. There is no need to define the value in 'main.c'.
Does it mean that when the function is called it somehow 'inherits' all variables defined in translation unit where it's defined?
No. (The term 'inheritance' has more to do with 'object oriented' environments. The term 'inheritance' is rarely used in C).