6

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.

igntec
  • 1,006
  • 10
  • 24

2 Answers2

4

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.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • I don't understand where I tried to redeclare a variable in the same scope with different types. I have 'static int a' and 'extern int a'. – igntec Aug 14 '16 at 11:43
  • @igntec I somehow missed `float a` prior to editing. I added comments and printfs to your example to illustrate what is going on. – Sergey Kalinichenko Aug 14 '16 at 12:12
  • @igntec When you "shadow" variables with locals, the type does not matter. When you "un-shadow" them with `extern` or `static`, the type does matter (it needs to match the one from the top, otherwise it's a compile-time error). – Sergey Kalinichenko Aug 14 '16 at 12:31
  • 1
    It works and it appears to be top 'a', but whether this is undefined behavior or not? Since we have stack variable 'int b' with no linkage, 'extern int b' should have external linkage. C standard says: If, within a translation unit, the same identifier appears with both internal and external linkage, the behavior is undefined. – igntec Aug 14 '16 at 12:33
  • http://stackoverflow.com/questions/29904114/cant-understand-the-declaration-3-in-the-example-of-basic-link-6-c14 Answer to this question illustrates an issue. – igntec Aug 14 '16 at 12:36
  • It seems that pedantically no one really cares whether it is undefined behavior or not and in practice things are like you described. – igntec Aug 14 '16 at 12:46
  • @igntec It is not an undefined behavior. See C99 standard, section 6.2.2, part 4: "For an identifier declared with the storage-class specifier `extern` in a scope in which a prior declaration 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." Your example follows this to the point. – Sergey Kalinichenko Aug 14 '16 at 12:46
  • In your example should we consider 'int b = 321' or 'static int b' as a 'prior declaration of that identifier'? – igntec Aug 14 '16 at 12:49
  • 2
    @igntec According to the standard section quoted above, it's `static int b`, not `int b = 321`, because the standard says "if the prior declaration specifies internal or external linkage". `static int b` specifies internal linkage, while `int b = 321` has no linkage. – Sergey Kalinichenko Aug 14 '16 at 12:58
  • But it also says that 'If no prior declaration is visible, or if the prior declaration specifies no linkage, then the identifier has external linkage'. So we can interpret 'int b = 321' as a prior declaration with no linkage. – igntec Aug 14 '16 at 13:04
  • 1
    @igntec You are right, I guess they should be more clear about what happens when two prior declarations, with and without linkage, are visible. – Sergey Kalinichenko Aug 14 '16 at 13:15
  • 1
    They talk only about one prior declaration and it seems logically that the prior identifier is the one that would be found by name lookup. Since stack variable shadows global it would be found first. – igntec Aug 14 '16 at 13:28
  • @SergeyKalinichenko As I understand, the C2x is not "more clear about what happens when two prior declarations, with and without linkage, are visible". Is that correct? – pmor Feb 23 '22 at 21:32
  • Actually the "if the prior declaration specifies internal or external linkage" does not mean that `static int b` is the prior declaration. I think that the term "prior declaration" should be clear. I guess that `int b = 321` is the prior declaration. – pmor Feb 23 '22 at 21:51
  • Though about "prior declaration": prior in which direction: top-to-bottom or bottom-to-top? – pmor Feb 23 '22 at 21:58
  • @igntec It seems that I've figured it out: `int b = 321` cannot be prior declaration, because for `extern int b;` the identifier `b` (from `int b = 321`) is _not_ visible. As footnote 32 says: "As specified in 6.2.1, the later declaration might hide the prior declaration". The `b` "declared in the outer scope is hidden (and not visible) within the inner scope". – pmor Mar 04 '22 at 10:17
  • @igntec Ah, it seems that I've missed it: the `int b = 321;` _is_ a prior declaration, but it is _invisible_. The "visible / invisible" doesn't change the fact that it is a prior declaration. However, the question about direction (see above) remains. – pmor Mar 05 '22 at 10:00
1

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.

igntec
  • 1,006
  • 10
  • 24
  • Re: "we have undefined behavior": just to precise: because "the same identifier appears with both internal and external linkage". – pmor Feb 23 '22 at 22:36