5
votes

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.

What is not clear is whether previous identifier to be considered must have the same type (note: C++ standard explicitly says "entity with the same name and type"). For example:

static int a;   // internal linkage

void f()
{
    float a;      // no linkage, instead of 'int a' we have 'float a'

    {
        extern int a;     // still external linkage? or internal in this case?
        a = 0;            // still unresolved external?
    }
}

I tried to test it with different compilers but it seems that linkage subject is not the one with great solidarity.

2
Maybe 6.7/4: "All declarations in the same scope that refer to the same object or function shall specify compatible types." ā€“ Kerrek SB
@KerrekSB - Yup. That should basically be an answer :) ā€“ Oliver Charlesworth

2 Answers

3
votes

C uses flat name space for all its globals. Unlike C++, which requires the linker to pay attention to the type of your global variables (look up name mangling for more info on this), C puts this requirement onto programmers.

It is an error to re-declare a variable with a different type when changing the linkage inside the same translation unit.

I will use your example with a small addition

static int a;   // internal linkage
static int b;   // internal linkage

void f()
{
    float a = 123.25; // this variable shadows static int a
    int b = 321;      // this variable shadows static int b

    { // Open a new scope, so the line below is not an illegal re-declaration
        // The declarations below "un-shadow" static a and b
        extern int a; // redeclares "a" from the top, "a" remains internal
        extern int b; // redeclares "b" from the top, "b" remains internal
        a = 42;       // not an unresolved external, it's the top "a"
        b = 52;       // not an unresolved external, it's the top "b"
        printf("%d %d\n", a, b); // static int a, static int b
    }
    printf("%f %d\n", a, b);     // local float a, int b
}

This example prints

42 52
123.250000 321

When you change the type across multiple translation units, C++ will catch it at the time of linking, while C will link fine, but produce undefined behavior.

0
votes

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.