At file scope, this:
int i;
Is a tentative definition since there is no initializer. It will be considered an external definition if no other definition appears.
When you then do this:
int i = 27;
This constitutes an external definition for i
which matches the prior tentative definition.
These terms are defined in section 6.9.2 p1 and p2 of the C standard:
1 If the declaration of an identifier for an object has file scope and an initializer, the declaration is an external
definition for the identifier.
2 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 initialize requal to 0.
Your second code snippet defines a variable in block scope (not file scope), then defines it again in the same scope. That constitutes a variable redefinition.