I have the following testing program:
// manager.h
#pragma once
#include <list>
namespace testapp
{
class Manager
{
public:
static Manager& instance();
void addItem(int val);
private:
std::list<int> m_items;
};
template <int N>
class Helper
{
public:
Helper()
{
Manager::instance().addItem(N);
}
};
}
// manager.cpp
#include "manager.h"
namespace testapp
{
static Manager s_global_instance;
Manager& Manager::instance()
{
return s_global_instance;
}
void Manager::addItem(int val)
{
m_items.push_back(val);
}
}
// main.cpp
#include <iostream>
#include "manager.h"
namespace testapp
{
Helper<1> helper;
}
int main()
{
std::cout << "It works!\n";
}
When I build this app with the following command line:
g++ main.cpp manager.cpp -std=c++2a -o TestApp
I got Segmentation fault, which is triggered in m_items.push_back(val).
However, if I build this app with different source order:
g++ manager.cpp main.cpp -std=c++2a -o TestApp
The program works as expected. I can the output It works! without any error.
After some investigation, I found that the problem is s_global_instance seems not fully initialized when Helper<1> helper gets instantiated. What I mean here is the member std::list<int> m_items is not initialized (properly) when global static variable s_global_instance is used. When addItem gets called, I stepped into push_back, I found that the head node is not actually initialized. It gets initialized after addItem getting called.
If I change static Manager s_global_instance to a local static variable like this:
Manager& Manager::instance()
{
static Manager _instance;
return _instance;
}
It's always working. No segmentation fault no matter which command line I use as showing above. The source file order doesn't matter any more.
My questions are here:
- Why compiling order matters when global static variable is used?
- Why local static variable works?
- Is this issue related to template instantiation?
- What is the initialization order when static variable, global static variable, global variable and template involved?
s_global_instanceandhelper, defined in two different translation units. Both have non-trivial constructors. These constructors are guaranteed to be called some time beforemain, but it's unspecified which one is called first. Whenhelperhappens to be constructed first, its constructor calls a method ons_global_instancewhose lifetime hasn't started yet, whereupon the program exhibits undefined behavior.helperconstructor runs and callsManager::instance()which in turn causes_instanceto be constructed.Helpera non-template class that just hard-codesaddItem(1), and should observe the same behavior.