I think I have an answer. I will write down on linkage subject in general.
C standard says:
In the set of translation units each declaration of a particular
identifier with external linkage denotes the same entity (object or function).
Within one translation unit, each declaration of an identifier with
internal linkage denotes the same entity.
C++ standard says:
When a name has external linkage, the entity it denotes can be
referred to by names from scopes of other translation units or from
other scopes of the same translation unit. When a name has internal
linkage, the entity it denotes can be referred to by names from other
scopes in the same translation unit.
This has two implications:
- In the set of translation units we cannot have multiple distinct external entities with the same name (save for overloaded functions in C++), so the types of each declaration that denotes that single external entity should agree. We can check if types agree within one translation unit, this is done at compile-time. We cannot check if types agree between different translation units neither at compile-time nor at link-time.
Technically in C++ we can violate in the set of translation units we cannot have multiple distinct external entities with the same name
rule without function overloading. Since C++ has name mangling that encodes type information it is possible to have multiple external entities with the same name and different types. For example:
file-one.cpp:
int a; // C decorated name: _a
// C++ decorated name (VC++): ?a@@3HA
//------------------------------------------------
file-two.cpp:
float a; // C decorated name: _a
// C++ decorated name (VC++): ?a@@3MA
Whereas in C this will really be one external entity, code in first unit will treat it as int
and code in second unit will treat it as float
.
- In one translation unit we cannot have multiple distinct internal entities with the same name (save for overloaded functions in C++), so the types of each declaration that denotes that single internal entity should agree. We check if types agree within translation unit, this is done at compile-time.
Now we will move closer to the question.
C++ standard says:
The name of a function declared in block scope and the name of a
variable declared by a block scope extern declaration have linkage. If
there is a visible declaration of an entity with linkage having the
same name and type, ignoring entities declared outside the innermost
enclosing namespace scope, the block scope declaration declares that
same entity and receives the linkage of the previous declaration. If
there is more than one such matching entity, the program is
ill-formed. Otherwise, if no matching entity is found, the block scope
entity receives external linkage.
// C++
int a; // external linkage
void f()
{
extern float a; // external linkage
}
Here we do not have previous declaration of entity with the same name (a
) and type (float
) so the linkage of extern float a
is external. Since we already have int a
with external linkage in this translation unit and name is the same, types should agree. In this case they don't, hence we have compile-time error.
// C++
static int a; // internal linkage
void f()
{
extern float a; // external linkage
}
Here we do not have previous declaration of entity with the same name (a
) and type (float
) so the linkage of extern float a
is external. It means that we have to define float a
in another translation unit. Note that we have the same identifier with external and internal linkage within one translation unit (I don't know why C considers this undefined behavior since we can have internal and external entity with the same name in different translation units).
// C++ (example from standard)
static int a; // internal linkage
void f()
{
int a; // no linkage
{
extern int a; // external linkage
}
}
Here the previous declaration int a
has no linkage, so extern int a
has external linkage. It means that we have to define int a
in another translation unit.
C standard says:
For an identifier declared with the storage-class specifier extern in
a scope in which a prior declaration of that identifier is visible,31)
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. If no prior declaration is
visible, or if the prior declaration specifies no linkage, then the
identifier has external linkage.
So we can see that in C only name is considered (without type).
// C
int a; // external linkage
void f()
{
extern float a; // external linkage
}
Here the previous declaration of identifier a
has external linkage, so the linkage of extern float a
is the same (external). Since we already have int a
with external linkage in this translation unit and name is the same, types should agree. In this case they don't, hence we have compile-time error.
// C
static int a; // internal linkage
void f()
{
extern float a; // internal linkage
}
Here the previous declaration of identifier a
has internal linkage, so the linkage of extern float a
is the same (internal). Since we already have static int a
with internal linkage in this translation unit and name is the same, types should agree. In this case they don't, hence we have compile-time error. Whereas in C++ this code is fine (I think type match requirement was added with function overloading in mind).
// C
static int a; // internal linkage
void f()
{
int a; // no linkage
{
extern int a; // external linkage
}
}
Here the previous declaration of identifier a
has no linkage, so extern int a
has external linkage. It means that we have to define int a
in another translation unit. However GCC decided to reject this code with variable previously declared 'static' redeclared 'extern'
error, probably because we have undefined behavior according to C standard.