0

I'm using std::make_unique (std::make_shared) to instantiate a class that takes a parameter. This parameter shall be initialized by a static constant.

If this static constant is initialized by a constant initializer (inside class definition) and not defined correctly according requirement from standard (outside class definition) then optimization -O0 shows a linker error but any optimization level (-O2, -O3, -Os) doesn't show any error. The compiled program runs.

I know that the missing static constant definition is an error. Thanks to this question and this question.

Why the linker error shows up only with -O0 if I remove the line "size_t const Foo::bufSize;"? Is this an error within gcc?

I've tested this code with: GCC 5.3.0 for x86 Windows (MinGW) GCC 6.3.0 for x86 Linux (Debian Stretch)

My simplified example code:

#include <iostream>
#include <cstdint>
#include <memory>

class Bar {
  public:
    Bar(size_t const size)
    : barSize(size) { std::cout << "Bar::Bar: Size: " << barSize << std::endl; }

    ~Bar(void) = default;
  private:
   size_t barSize;
};

#define USE_CONST_INITIALIZER 1

class Foo {
  public:
#if (USE_CONST_INITIALIZER == 1)
    static size_t const bufSize = 4096U;
#else
    static size_t const bufSize;
#endif

    Foo(void)
    : spBuffer(std::make_unique<Bar>(Foo::bufSize)) // -Os compiles and links, -O0 shows linker error
    //: spBuffer(new Bar(Foo::bufSize)) // no errors, -Os and -O0 compiles and links
    {
      std::cout << "Foo::Foo: constructed." << std::endl;
    }

    ~Foo(void) = default;

  private:
    std::unique_ptr<Bar> spBuffer;
};

#if (USE_CONST_INITIALIZER == 1)
size_t const Foo::bufSize;    // This definition (btw. required by standard) is essential if compiled with -O0
#else
size_t const Foo::bufSize = 4096U;
#endif

int main(void) {
  std::cout << "Hello world!" << std::endl;
  Foo foo;
  return 0;
}
AKr143
  • 3
  • 2

1 Answers1

0

The variable Foo::bufSize is odr-used because passing it to the make_unique function causes it to be passed by forwarding reference, which requires it to have an address. (Note that writing (int)Foo::bufSize would prevent it from being odr-used.)

However, since the compiler can see the definition of make_unique and knows that Bar takes its constructor argument by value, at some optimization level, it may be clever enough to eliminate the call to make_unique, simply inlining the value 4096U in a call to the constructor of Bar, or may even eliminate the Bar object altogether as the initialization of barSize is a dead store. In such a case, it would not need to emit a reference to Foo::bufSize, and no linker error would occur.

The compiler and linker are not required to tell you that you violated the ODR, since ODR violations render your program "ill-formed, no diagnostic required".

Brian Bi
  • 111,498
  • 10
  • 176
  • 312
  • 1
    Side note: +Foo::bufSize has the same effect, but less typing and less room for error (could be used for other types as well) – SergeyA Feb 27 '19 at 22:02