To understand the difference, you need to get familiar with a concept called tentative definition in C. To quote the C standard:
C11, draft, §6.9.2, External object definitions
A declaration of an identifier for an object that has file scope
without an initializer, and without a storage-class specifier or with
the storage-class specifier static, constitutes a tentative
definition. If a translation unit contains one or more tentative
definitions for an identifier, and the translation unit contains no
external definition for that identifier, then the behavior is exactly
as if the translation unit contains a file scope declaration of that
identifier, with the composite type as of the end of the translation
unit, with an initializer equal to 0.
What you have in the first snippet is only a tentative definition for i
. You can have as many tentative definitions for an object as you want (but only one definition is allowed):
int i; // tentative definition
int i; // tentative definition
int i; // tentative definition
int main(void) {
return 0;
}
is valid.
Here, i
has external linkage and tentatively defined. If i
is defined in somewhere in the same translation unit, then that'll be the actual definition of i
. If there's no other definition of i
is found in the translation unit, then this becomes the full definition as if it was defined like:
int i = 0;
int main(void) {
return 0;
}
But the second snippet int i;
is not a tentative definition. Only objects with external linkage can be defined tentatively. In second snippet, The declaration extern int i;
says i
is defined elsewhere with external linkage. But the next line int i;
says i
is defined with no linkage (local automatic variables do not have any linkage -- this is not a tentative definition). So there's a conflict of definition for i
. Hence, the first one snippet is fine but second isn't.