1

Is it valid and well defined (not asking whether it's pretty) to call member functions of an object in global namespace before this object's constructor has been executed (for example from a global new replacement) - and when this member function executes, are the member variables of that object guaranteed to be zero initialized and is it no error to access (and even write to) them? for example:

#include <iostream>
#include <memory>

using namespace std;

struct A {
  A() {
    new int();
  }
};

A a_;

struct B {
  B() : var(10) {
  }
  void print() {
    printf("var is %d\n", var);
    var = 5;
  }
  int var;
};

B b_;

void* operator new(size_t bytes) {
    b_.print();
    b_.print();
    return malloc(bytes);
}

int main()
{
    b_.print();
    return 0;
}

this compiles and prints

var is 0
var is 5
var is 10

but is it defined behaviour?

matthias_buehlmann
  • 4,641
  • 6
  • 34
  • 76

1 Answers1

2

It is true that zero-initialization happens up front, but it is undefined behavior to invoke non-static class methods before their class instance is constructed. The method call itself becomes undefined behavior.

Objects in global (static) scope that are defined in the same translation unit are constructed in their declaration order (and destroyed in the opposite order).

Here, a_ gets constructed first, and then ends up invoking the new overload, which invokes a method of a class instance that has not been constructed yet, which is undefined behavior.

And if the two objects get defined in different translation units you end up with the static initialization order fiasco.

Even if you define b_ first, this is still undefined behavior unless you can guarantee that nothing in any other translation unit ends up calling this new overload. This includes the C++ library. Many C++ implementations' library do allocate some storage for their own use, so this is fairly likely to be undefined behavior as well, in the end, even if the definition order gets reversed, here.

Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
  • does that in turn mean it's impossible to use any global book-keeping structures for a replacement new, because they will most likely be accessed before their own constructor has been executed? – matthias_buehlmann Sep 06 '20 at 13:53
  • Default-initialization and zero-initialization occurs early enough so that if you can make sure that your bookkeeping consists of trivial zero or default-initialized PODs, and not objects with class methods, then this should be pedantic enough. – Sam Varshavchik Sep 06 '20 at 13:55
  • @user1282931 Such structure can be made a static variable inside the `new` replacement. But they are not fully global. In that case a you can make a function `Singleton getInstance()` that returns its static variable. This will work and is guaranteed to return initialized object and it is global. – Quimby Sep 06 '20 at 13:57
  • okay - so if I remove the member function `print` as well as the constructor of B and instead add a global function `void print() { printf("var is %d\n", b_.var); b_.var = 5; }` then the program is well defined? – matthias_buehlmann Sep 06 '20 at 13:58
  • @Quimby can such a static variable inside new be thread_local ? – matthias_buehlmann Sep 06 '20 at 13:59
  • 1
    The reference is [class.dtor]: *For an object with a non-trivial constructor, referring to any non-static member or base class of the object before the constructor begins execution results in undefined behavior.* – Serge Ballesta Sep 06 '20 at 13:59
  • 1
    @user1282931 Of course, `int& f(){static thread_local int n=10; return n;}` is valid code. Moreover `thread_local` implies `static`. But be careful that placing it inside `new` because then its initialization cannot invoke `new` again. – Quimby Sep 06 '20 at 14:03