Question

Belloc on Thu, 31 May 2018 21:18:45


Consider the snippet below:

File main.cpp

#include<iostream>
struct S { static const int x = 1; } s;
//const int S::x;

int main() {
	std::cout << &s.x << '\n';
}

File other.cpp

struct S { static const int x = 1; };
//const int S::x;
Note that the definition of the variable S::x has been commented out in the two files main.cpp and other.cpp. But this code runs, printing the address of the variable S::x. How come? Does the compiler allocate storage for this variable while compiling one file, without knowing whether the same variable is already defined in the next file to be compiled? As far as I know, the compiler doesn't know whether a variable has already been defined in a previous compilation, or whether it's going to be defined in the next file to be compiled.  The only explanation I have for this is that the linker is allocating storage for this variable. But that doesn't seem a valid alternative, as I didn't use the link option "Link time code generation" for this project. What am I missing?

Replies

Barry-Schwarz on Thu, 31 May 2018 22:28:57


I don't understand your question.  other.cpp has no code that would affect the performance of main().  It does define the type struct S but that type is not related to the type struct S defined in main.cpp

Both s and main() are defined in main.cpp.  s is defined at file scope giving it external linkage and static duration.  Why do you think main() should have any trouble printing the address of s.x?

Igor Tandetnik on Thu, 31 May 2018 22:45:31


On 5/31/2018 5:18 PM, Belloc wrote:

Note that the definition of the variable S::x has been commented out in the two files main.cpp and other.cpp. But this code runs, printing the address of the variable S::x. How come?

Your program contains One Definition Rule violation, and is therefore ill-formed, no diagnostic required. For what it's worth, GCC and Clang produce an error for this example.

Does the compiler allocate storage for this variable while compiling one file, without knowing whether the same variable is already defined in the next file to be compiled? As far as I know, the compiler doesn't know whether a variable has already been defined in a previous compilation, or whether it's going to be defined in the next file to be compiled.  The only explanation I have for this is that the linker is allocating storage for this variable. But that doesn't seem a valid alternative, as I didn't use the link option "Link time code generation" for this project. What am I missing?

If I had to guess, I'd assume the compiler allocates storage for s.x in every translation unit where it's odr-used, and instructs the linker to merge all those instances into one (in other words, pick one arbitrarily and discard all others). This is the mechanism used for static members of class templates (and, in modern compilers, for inline variables). Consider:

// SomeHeader.h

template <typename T>
struct S {
    static T x;
};
template <typename T>
T S<T>::x;

// First.cpp
#include "SomeHeader.h"
int* p = &S<int>::x;

// Second.cpp
#include "SomeHeader.h"
int* q = &S<int>::x;

Compiler and linker conspire to make sure there's only one copy of S<int>::x in the program; that p == q.

Belloc on Thu, 31 May 2018 22:58:40


Because the static data member S::x is not defined in main.cpp, nor in other.cpp. According to [basic.def.odr]/10, the code should contain at least one definition of the variable S::x, which is odr-used by the expression

std::cout << &s.x << '\n';
For example, if I had one of the statements, `const int S::x;` included in the code, either in main.cpp, or in other.cpp, the code would be well-formed.

But, as no diagnostic is required, the code seems to be valid,  but I don't understand how the compiler could allocate the storage for such a variable, without its definition, as I described in my first post.

You wrote:

"It does define the type struct S but that type is not related to the type S defined in main.cpp"

That's not exactly correct. The type S has to be defined in other.cpp, in the same way it is defined in main.cpp, otherwise the code would be ill-formed. And there is nothing wrong with a file containing just the definition of a type. For instance, if you want to define the variable S::x, in other.cpp, you'll need the definition of S as defined above, and this type will be equivalent to the type defined in main.cpp. The name of a type has internal linkage in C++.


Darran Rowe on Thu, 31 May 2018 23:32:33


By the looks of it, even though 12.2.3.2 paragraph 3 of the C++ 17 standard states:

"If a non-volatile non-inline const static data member is of integral or enumeration type, its declaration in the class definition can specify a brace-or-equal-initializer in which every initializer-clause that is an assignment-expression is a constant expression (8.20). The member shall still be defined in a namespace scope if it is odr-used (6.2) in the program and the namespace scope definition shall not contain an initializer. An inline static data member may be defined in the class definition and may specify a brace-or-equal-initializer. If the member is declared with the constexpr specifier, it may be redeclared in namespace scope with no initializer (this usage is deprecated; see D.1). Declarations of other static data members shall not specify a brace-or-equal-initializer. "

all compilers seem to not require the definition as an extension. The clang compiler accepts not having that definition too.

Tim Roberts on Fri, 01 Jun 2018 00:42:56


> Note that the definition of the variable S::x has been commented out in the two files ...

No, it certainly has not.  The definition of the variable is within the struct declaration itself -- the constant 1.  A "static const int" is essentially an enum; it is a compile-time constant.  Because you're taking the address of it, the compiler allocates a place for it, but the location is unrelated to the structure s.

You can verify that by printing "sizeof(s)" (which is 1), and by printing &s and &s::x.  You'll discover those are two separate addresses.

If you eliminate the "const", then your statements would be valid.  The file would not compile, because S::x was not defined.

Igor Tandetnik on Fri, 01 Jun 2018 01:31:01


On 5/31/2018 8:42 PM, Tim Roberts [MVP] wrote:

Note that the definition of the variable S::x has been commented out in the two files ...

No, it certainly has not.  The definition of the variable is within the struct declaration itself -- the constant 1.

False. That's a declaration but not a definition.

[basic.def]/2 A declaration is a definition unless
...
(2.3) — it declares a non-inline static data member in a class definition

[class.static.data]/2 The declaration of a non-inline static data member in its class definition is not a definition...

A "static const int" is essentially an enum; it is a compile-time constant.

Compile-time constants don't have addresses though.

Belloc on Fri, 01 Jun 2018 11:12:40


If I had to guess, I'd assume the compiler allocates storage for s.x in every translation unit where it's odr-used, and instructs the linker to merge all those instances into one (in other words, pick one arbitrarily and discard all others). This is the mechanism used for static members of class templates (and, in modern compilers, for inline variables). 
That's the answer. Thank you.

Tim Roberts on Mon, 04 Jun 2018 20:43:34


S::x would not have an address and would not need a definition if it were not odr-used.

Igor Tandetnik on Mon, 04 Jun 2018 20:58:22


On 6/4/2018 4:43 PM, Tim Roberts [MVP] wrote:

S::x would not have an address and would not need a definition if it were not odr-used.

True, but I fail to see the relevance to the issue at hand. The OP's example does odr-use it.