5

I understand that static const members have to have out-ouf-class definition it they are odr-used. But the thing is, my program compiles and run just fine even without members definition.

Let's take a look at this example from C++ FAQ:

class AE
{
public:
    static const int c6 = 7;
    static const int c7 = 31;
};
const int AE::c7;   // definition

void f()
{
    const int* p1 = &AE::c6;    // error: c6 not an lvalue
    const int* p2 = &AE::c7;    // ok

    cout << *p1 << endl;
}

int main()
{
    f();

    const int* p1 = &AE::c6; 
    std::cout << p1 << "\n";

    return 0;
}


//RESULT:
// 7
// 00007FF735E7ACE8

I see no error whatsoever. I use Visual Studio 2015, and this code compiles and runs just fine.

My question: Is this specific to msvc or there are some language changes that I'm not aware of?

UPDATE: This is not a duplicate, as I said at the very beginning: I do understand how this suppose to work, I don't understand why it doesn't work as it should.

Brad Larson
  • 170,088
  • 45
  • 397
  • 571
  • 4
    Possible duplicate of [Defining static const integer members in class definition](http://stackoverflow.com/questions/3025997/defining-static-const-integer-members-in-class-definition) – Yves Feb 01 '17 at 14:54
  • 2
    Do note that with optimizations turned on gcc also does not give an error on `const int* p1 = &AE::c6;` as it just optimizes it away – NathanOliver Feb 01 '17 at 14:54
  • Windows c++ compiler is not good because it always try to help you nicely, meaning that it will do more implicitly. `c6` is a declaration, not a definition. – Yves Feb 01 '17 at 14:56
  • Well, the answer is simple: msvc does it for you... And that's why I don't like msvc. – Yves Feb 01 '17 at 15:00
  • 2
    @Petrof: First, you must understand that this is never a compiler error. It's a linker error if the result of compilation still uses the variable's address, but you have to actually use it, not just syntactically, or your odr-use may be removed by dead-code elimination. – Ben Voigt Feb 01 '17 at 15:17
  • 2
    This is not just an MSVS issue. See: http://coliru.stacked-crooked.com/a/03e908b91d12d0b5 – NathanOliver Feb 01 '17 at 15:20
  • @Petrof: Printing the value of a constant isn't odr-use either. Update your question with the code you are thinking proves your point, we can't explain the behavior of things we can't see. – Ben Voigt Feb 01 '17 at 15:22
  • const integers are special snowflakes in C++. – bolov Feb 01 '17 at 15:36
  • @Petrof: We've already explained why this code is not a good test, namely, it's all dead code, and the odr-use doesn't survive dead-code elimination. If you're unwilling to provide better code, you won't learn. – Ben Voigt Feb 01 '17 at 15:54
  • @BenVoigt: I've provided some code in my answer showing that it is not the result of dead code elimination. – Serge Ballesta Feb 01 '17 at 17:03
  • @Petrof: It is not an issue with the compiler. The standard specifically says "no diagnostic required". – Ben Voigt Feb 01 '17 at 18:35

3 Answers3

4

The program explicitely violates the one definition rule as you have stated in your question. But the standard does not require a diagnostic in this case. This is explicit in 3.2 One definition rule [basic.def.odr] §4 (emphasis mine)

Every program shall contain exactly one definition of every non-inline fonction or variable that is odr-used in that program; no diagnostic required.

and 1.4 Implementation compliance [intro.compliance] §2.3 says:

If a program contains a violation of a rule for which no diagnostic is required, this International Standard places no requirement on implementations with respect to that program.

That means that gcc is right to choke on that program because one rule has been violated. But it is not a bug when MSVC accepts it as a compiler extension(*) without even a warning because the standard places no requirement here.

We are in the compile time undefined behaviour named ill-formed program, no diagnostic required. A compiler implementation is free to do what it wants:

  • reject the program
  • automatically fix the error and generate the code as if the definition was present (for current case)
  • remove the offending line[s] if it makes sense and makes the program compilable - even if what is generated is not what the programmer expected
  • compile it into a program that ends in a run-time error
  • [add whatever you think of, but I have never seen a compiler able to hit my cat...]

(*) more exactly, it is a compiler extension if it is documented. Unfortunately, I currently have no MSVC compiler full documentation, so I can not say whether it is documented and is a conformant extension, or is not and is just hmm... one possible execution of the program

Here is a slight variation on OP's code demonstrating that the compiler did provide a definition:

#include <iostream>

class AE {
    // ...
public:
    static const int c6 = 7;
    static const int c7 = 31;
};
const int AE::c7;   // definition

int main()
{
    const int* p1 = &AE::c6;    // error: c6 not an lvalue
    const int* p2 = &AE::c7;    // ok
    // ...
    std::cout << *p1 << "(" << p1 << ") - " << *p2 << "(" << p2 << ")" << std::endl;
    return 0;
}

The output is (with an old MSVC 2008, debug mode to avoid as much optimization as possible):

7(00DF7800) - 31(00DF7804)

that is expected values and consecutive addresses

With same code, Clang 3.4 complains (as expected) at link time with

undefined reference to `AE::c6'
Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
  • Quick cat or slow compilers? ;) – NathanOliver Feb 01 '17 at 15:42
  • It's not really a compiler extension in MSVC, just that the compiler doesn't pass enough information forward to the linker to diagnose the error. Which is you point out, is entirely compliant behavior. – Ben Voigt Feb 01 '17 at 15:52
  • @BenVoigt: When I remember how linkers used to be in the old days, they take a bunch of translation units containing *defined* symbols, *required* symbols and *mergeable* ones. If MSVC can link that without error, I assume that it made the symbol mergeable, which is more or less the same as a definition. – Serge Ballesta Feb 01 '17 at 16:02
  • @SergeBallesta: There's another category: no usage whatsoever. Given that the code in the question is dead code, this option seems the most likely. – Ben Voigt Feb 01 '17 at 16:03
  • @Petrof: Having these statements in `main()` would force the code to be used: `const int* p1 = &AE::c6; std::cout << p1 << "\n";` – Ben Voigt Feb 01 '17 at 16:11
  • @BenVoigt: I've tested with an old MSVC2008 express, and the compiler did generate a definition, with an address immediately preceding the one for `AE::c7` – Serge Ballesta Feb 01 '17 at 16:21
  • Please note that in my testing, the compiler resolved `*p1` to a constant `7` and didn't actually perform a memory access. That is, if you don't print `p1`, `clang` and `g++` will (with certain optimization settings) no longer have an undefined reference error, even when you do print `*p1`. – Ben Voigt Feb 01 '17 at 18:35
  • @BenVoigt: that's the reason why I print both `p1` **and** `*p1` :-) – Serge Ballesta Feb 01 '17 at 22:46
1

It just so happens that your compiler has applied some optimisations that mean it doesn't require you to follow the One Definition Rule in this case.

However, the C++ standard still requires you to do so, so your program has undefined behaviour. The standard does not require a compiler to tell you when you violate this particular rule, so it's chosen not to. Indeed, it would have to put in extra effort to detect the problem.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • After OP's edit, it cannot be because the variable has been optimized out. It **has** a valid address. So MSVC has decided to automatically and silently generate one because of the *no diagnostic required*. – Serge Ballesta Feb 01 '17 at 16:25
  • @Serge: You can't really observe whether the address is valid until you attempt to dereference it, which the OP doesn't do. ;) – Lightness Races in Orbit Feb 01 '17 at 16:38
  • @Serge: Okay there you go :) – Lightness Races in Orbit Feb 01 '17 at 16:56
  • @LightnessRacesinOrbit: And when you do dereference the pointer, the compiler performs constant propagation and still doesn't access the variable. At least, `clang` was smart enough in my testing on rextester.com. Maybe if you did `*reinterpret_cast(reinterpret_cast(p1))`, that should defeat constant propagation. – Ben Voigt Feb 01 '17 at 18:38
0

The C++ standard says ([basic.def.odr] 3.2 paragraph 2) "A variable whose name appears as a potentially-evaluated expression is odr-used unless it is an object that satisfies the requirements for appearing in a constant expression (5.19) and the lvalue-to-rvalue conversion (4.1) is immediately applied."

Read this for further information

Steranoid
  • 30
  • 3