8

I compiled the following program with both g++ (7.1) and clang++ (xcode 9.0) with -std=c++11 -Wall and get the result:

g++

0x10052c050
0x10052c040
0x10052c040

clang++

0x108b74024
0x108b74018
0x108b74018

It means that the extern int a[]; and static int a[3]; declares same entity and have the same linkage (internal linkage).

//a.cpp
#include <stdio.h>
int a[3];
void f()
{
    printf("%p\n", (void*)a);
};
//b.cpp
extern void f();
static int a[3];
void g()
{
    printf("%p\n", (void*)a);
    extern int a[];
    printf("%p\n", (void*)a);
}
int main(int argc, char* argv[])
{
    f();
    g();
    return 0;
}

but, in C++11 [3.9/6]:

... The declared type of an array object might be an array of unknown size and therefore be incomplete at one point in a translation unit and complete later on; the array types at those two points (“array of unknown bound of T” and “array of N T”) are different types. ...

int a[3]; and int a[]; are different types,

In C++11 [3.5/6]:

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.

"having the same name and type“ not compatible, so extern int a[]; shall not receives the linkage of the previous declaration(static int a[3];), and so, "Otherwise, if no matching entity is found, the block scope entity receives external linkage." is compatible.

My question is:

  • Why the result of the compiler is not consistent with the C++11 standard wording?
  • Or if my understanding is wrong, what's right?

Note: this question is different from error: extern declaration of 'i' follows declaration with no linkage

xskxzr
  • 12,442
  • 12
  • 37
  • 77
  • Maybe since `a` is odr-used with an incomplete type, so the program is ill-formed; no diagnostic required, thus has the strange behavior? – xskxzr Dec 16 '17 at 09:37

3 Answers3

1

This wording gap was fixed thanks to CWG 2372. (The example there is suspiciously similar to the one in this question; perhaps the submitter was the OP, someone who answered or commented here, or someone who happened to see this question.) The wording in C++20 reads:

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 such a declaration is attached to a named module, the program is ill-formed. If there is a visible declaration of an entity with linkage, ignoring entities declared outside the innermost enclosing namespace scope, such that the block scope declaration would be a (possibly ill-formed) redeclaration if the two declarations appeared in the same declarative region, 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. If, within a translation unit, the same entity is declared with both internal and external linkage, the program is ill-formed.

An exact match in type is no longer required. Instead,

extern int a[];

at block scope matches with

static int a[3];

in the innermost enclosing namespace scope because the two declarations satisfy the rules for the block scope declaration to redeclare the namespace scope declaration if the former were to follow the latter in the same declarative region. In other words, the test is to imagine that the extern declaration were moved to the namespace scope and then check whether it would be a redeclaration (even an ill-formed one).

In the case of variables, that test is satisfied as long as the names are the same. To wit, if we had the following at global scope:

static int a[3];
extern int a[];

the latter would be a redeclaration of the former, which is allowed to differ as to whether the array bound is present under [basic.link]/11. Therefore, extern int a[]; at block scope does indeed redeclare the preceding static int a[3]; in namespace scope.

Note that if the types differ:

static int a[3];       // namespace scope
extern const char* a;  // block scope

the latter would still be a redeclaration if it were moved to namespace scope, but an ill-formed one because the types don't match. So it's also an ill-formed redeclaration when at block scope.

The resolution of CWG 2372 is a DR, so it's considered retroactive as far back as applicable (in this case, all the way back to C++98).

Note that the wording has changed significantly in C++23, but I believe the rules have not changed in this area.

Brian Bi
  • 111,498
  • 10
  • 176
  • 312
0

"extern int a[]" does not FULLY define "a".

Updated after @Bob__ 's comment to my original post.

I get the same results on all three compilers I tried (up to change in pointer addresses) - thanks, @Bob__ . Since all of them are compiling should we say it is the language of the standard that needs updating?

  • @Shankar: "Since all of them are compiling should we say it is the language of the standard that needs updating?" Well the issue is, what does the standard *actually* say should happen here? Are there any other standards paragraphs that are relevant? Is the standard saying what committee intends it to say in regards to this program, or is this a defect in the standard? It's entirely possible that all 3 compilers are wrong according to the current wording of standard, and all 3 compilers should be patched, without a defect resolution for the standard. – Chris Beck Dec 16 '17 at 19:30
0

From the formal standpoint, the words that you missed are

the array types at those two points (“array of unknown bound of T” and “array of N T”) are different types.

It does not say

the array types (“array of unknown bound of T” and “array of N T”) are different types.

So basically both a declarations in your example are of an array type.

If this does not sound too convincing, then, IMHO, block scope extern declarations are not the most popular feature in the standard. For example, see this there the compilers implement totally different behavior than it is stated C++11,14,17 standards. And eventually in C++17 it is considered to be ill-formed to have an entity with both internal and external linkage for this case (that is not yet implemented by the compilers).

Second, from the practical standpoint, it feels like a name clash: you want to have two objects with static storage duration with the same name in the same scope. Why would you do that, what is practical goal that you achieve by this, even if that was possible, that would be super confusing for the code readability? Why not just name your internal static storage duration variable to something different from the external one?

Sorry that my post does not give a straight away answer, rather asks more question. Thank you for bringing an interesting case showing that the standard could be more clear on some points.

Yuki
  • 3,857
  • 5
  • 25
  • 43