Declaring the same identifier in different places involves scope. C 2018 6.2.1 says:
1 … The same identifier can denote different entities at different points in the program…
2 … For each different entity that an identifier designates, the identifier is visible (i.e., can be used) only within a region of program text called its scope…
3 …
4 … If an identifier designates two different entities in the same name space, the scopes might overlap. If so, the scope of one entity (the inner scope) will end strictly before the scope of the other entity (the outer scope). Within the inner scope, the identifier designates the entity declared in the inner scope; the entity declared in the outer scope is hidden (and not visible) within the inner scope.
Consider your first case, which I have cleaned up a little:
#include <stdio.h>
extern int i;
int main(void)
{
static int i = 5;
printf("%d\n", i);
}
In this, extern int i;
declares i
with file scope (outside of any function) but does not define it. Then static int i = 5;
inside main
declares a new i
with block scope (inside a function), and main
prints it. Inside main
, i
refers to the new object, and the earlier declaration is hidden.
This means your program contains an i
that is declared but not defined. That is okay because it does not actually use it. C 2018 6.9 5 says (emphasis added)
… If an identifier declared with external linkage is used in an expression (other than as part of the operand of a sizeof
or _Alignof
operator whose result is an integer constant), somewhere in the entire program there shall be exactly one external definition for the identifier; otherwise, there shall be no more than one.
To see there are actually two i
identifiers declared, try building this program:
#include <stdio.h>
extern int i;
void foo(void)
{
i = 3;
printf("foo: %d\n", i);
}
int main(void)
{
foo();
static int i = 5;
printf("%main: %d\n", i);
}
With common C implementations, the linker will complain there is an undefined symbol named i
. (Some platforms will show _i
in the linker error message.) Since this program uses the first i
, there must be a definition for it. Let’s give it one:
#include <stdio.h>
extern int i;
int i = 0;
void foo(void)
{
i = 3;
printf("foo: %d\n", i);
}
int main(void)
{
foo();
static int i = 5;
printf("%main: %d\n", i);
}
The output from this program is:
foo: 3
main: 5
because foo
sets the first i
to 3 and prints that, and then main
prints the value of the second i
, which is 5.
For your second example:
#include <stdio.h>
static int i = 5;
int main(void)
{
extern int i;
printf("%d\n", i);
}
static int i = 5;
declares i
to be an int
with static storage duration and internal linkage. Then extern int i;
is covered by C 2018 6.2.2 4:
For an identifier declared with the storage-class specifier extern
in a scope in which a prior dec- laration of that identifier is visible, if the prior declaration specifies internal or external linkage, the linkage of the identifier at the later declaration is the same as the linkage specified at the prior declaration.
Linkage is described in C 2018 6.2.2 1:
An identifier declared in different scopes or in the same scope more than once can be made to refer to the same object or function by a process called linkage.
So static int i = 5;
gives i
internal linkage, and the later extern int i;
uses the same linkage, and C 2018 6.2.2 2 tells us:
… Within one translation unit, each declaration of an identifier with internal linkage denotes the same object or function…
Thus, these two declarations refer to the same object. In a technical sense, there are two declarations of i
in different scopes, but they are made to refer to the same object, so there is just one object named i
.
(Some of the rules about scope, linkage, and multiple declarations are complicated due to the history of the C language. If we were developing it from scratch, we would likely design cleaner rules.)