5

If I have two static variables in different compilation units, then their initialization order is not defined. This lesson is well learned.

The question I have: are all the static variables already allocated, when the first one is being initialized. In other words:

static A global_a; // in compilation unit 1
static B global_b; // in compilation unit 2

struct A {
    A() { b_ptr = &global_b; }
    B *b_ptr;

    void f() { b_ptr->do_something(); }
}

int main() {
    global_a.f();
}

Will b_ptr point to a valid piece of memory, where B is allocated and initialized at the time of the execution of the main function? On all the platforms?

Longer story:

The compilation unit 1 is Qt library. The other one is my application. I have couple QObject derived classes, that I need to be able to instantiate by the class name string. For this I came up with a templated factory class:

class AbstractFactory {
public:
    virtual QObject *create() = 0;
    static QMap<const QMetaObject *, AbstractFactory *> m_Map;
}
QMap<const QMetaObject *, AbstractFactory *> AbstractFactory::m_Map; //in .cpp

template <class T>
class ConcreteFactory: public AbstractFactory {
public:   
    ConcreteFactory() { AbstractFactory::m_Map[&T::staticMetaObject] = this; }
    QObject *create() { return new T(); }
}

#define FACTORY(TYPE) static ConcreteFactory < TYPE > m_Factory;

Then I add this macro on every QObject subclass definition:

class Subclass : public QObject {
   Q_OBJECT;
   FACTORY(Subclass);
}

Finally I can instantiate a class by the type name:

QObject *create(const QString &type) {
    foreach (const QMetaObect *meta, AbstractFactory::m_Map.keys() {
        if (meta->className() == type) {
           return AbstractFactory::m_Map[meta]->create();
        }
    }
    return 0;
}

So the class gets a static QMetaObject instance: Subclass::staticMetaObject from the Qt library - it is auto-generated in Q_OBJECT macro I think. And then the FACTORY macro creates a static ConcreteFactory< Subclass > instance. ConcreteFactory in its constructor tries to reference of Subclass::staticMetaObject.

And I was pretty happy with this implementation on linux (gcc), until I compiled it with Visual Studio 2008. For some reason AbstractFactory::m_Map was empty on the runtime, and the debugger would not break at the factory constructor.

So this is where the smell of static vars referencing other static vars is coming from.

How can I optimize this code to avoid all these traps?

2
  • Well this code is quite different from your original question... in your original question, you stored the address of a global variable, but didn't access it until main, by which time the global was fully constructed. But now you are trying to use it from inside the constructor of a global ConcreteFactory object, which is much earlier than main. Commented Dec 7, 2010 at 18:21
  • In the detailed example I store the address of the static variable staticMetaObject. But it is accessed from main via the create function at the runtime - exactly as I described in the simplified example. I am not concerned with the static QMap<...> m_Map - in my real code is actually wrapped into a static function just as @Martin York suggested. So the detailed example is about referencing staticMetaObject from ConcreteFactory constructor. Commented Dec 7, 2010 at 19:18

5 Answers 5

5

Yes, the standard allows this.

There are a number of paragraphs in section [basic.life] which start out

Before the lifetime of an object has started but after the storage which the object will occupy has been allocated or, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, any pointer that refers to the storage location where the object will be or was located may be used but only in limited ways.

and there is a footnote which indicates that this specifically applies to your situation

For example, before the construction of a global object of non-POD class type

Sign up to request clarification or add additional context in comments.

3 Comments

Its nice to have section numbers. But well found.
I'm pretty sure that section names are pretty consistent across versions of the standard, but I don't have quite that level of comfort about the numbering. It's section 3.8 of the C++0x FCD for example.
Ooops. Missed that obvious name. :-) Its still early for me.
2

Short Answer: Its should work as you have coded it. See Ben Voigt Answer

Long Answer:

Do something like this:
Rather than let the compiler decide when globals are created, create them via static methods (with static function variables). This means that they will be deterministically created on first use (and destroyed in the reverse order of creation).

Even if one global uses another during its construction using this method gurantees that they will be created in the order required and thus be available for usage by the other (watch out for loops).

struct A
{
    // Rather than an explicit global use
    // a static method thus creation of the value is on first use
    // and not at all if you don't want it.
    static A& getGlobalA()
    {
        static A instance;  // created on first use (destroyed on application exit)
     // ^^^^^^ Note the use of static here.
        return instance;    // return a reference.
    }
  private:
    A() 
        :b_ref(B::getGlobalB())     // Do the same for B
    {}                              // If B has not been created it will be
                                    // created by this call, thus guaranteeing
                                    // it is available for use by this object
    }
    B&  b_ref;
  public:
    void f() { b_ref.do_something(); }
};

int main() {
    a::getGlobalA().f();
}

Though a word of warning.

  • Globals are an indication of bad design.
  • Globals that depend on other globals is another code smell (especially during construction/destruction).

2 Comments

compiler implementation you mean?
the OP asked about their allocation, and not initialization. Compare static int i with static int* i.
1

Yes. All are located in .data section, that is allocated at once (and it's not heap).

Putting it another way: if you are able to take its address, then it's OK, because it surely won't change.

1 Comment

The standard has no concept of .data sections.
1

If B has a constructor, like A has, then the order that they are called is undefined. So your code won't work unless you are lucky. But if B doesn't require any code to initialise it, then your code will work. It's not implementation-defined.

2 Comments

That's not the question being asked. The question being asked is weather the address of the variable global_b is available for use by the constructor of global_a (even if global_b has yet to be initialized by its constructor).
Yes, you're quite right. Sorry. Ignore my answer, go and read Ben Voigt's.
1

Ah, but the idea that static variables are "not initialised" is quite wrong. They're always initialised, just not necessarily with your initialiser. In particular, all static variables are created with value zero before any other initialisation. For class objects, the members are zero. Therefore global_a.b_ptr above will always be a valid pointer, initially NULL and later &global_b. The effect of this is that use of non-pointers is unspecified, not undefined, in particular this code is well defined (in C):

// unit 1
int a = b + 1;

// unit 2
int b = a + 1;

main ... printf("%d\n", a + b); // 3 for sure

The zero initialisation guarantee is used with this pattern:

int get_x() {
  static int init;
  static int x;
  if(init) return x;
  else { x = some_calc(); init = 1; return x; }
}

which assures either a non-return due to infinite recursion, or, correctly initialised value.

1 Comment

I often run into an issue where the automatic initialization of member variables works differently in debud and release. So if I forget to assign 0 (or NULL) to a member pointer, it might be not initialized in debug (i.e. contain garbage value). Does it work differently for static variables?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.