Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

c++ program crash when creating global instance of class whose constructor references a global variable

I'm trying to make a global instance of a class whose constructor references a global variable.

The program compiles without error. But when it is run, it crashes on the reference of the global variable.

How do I create a global instance of this class without the constructor crashing it?

Here is the SSCCE that I made:

/* main.cpp */
#include "TestClass.h"

// I need a global instance of TestClass
TestClass j;

int main()
{
    return 0;
}

-

/* C.h */
#ifndef C_H_INCLUDED
#define C_H_INCLUDED

#include <string>

// global
extern const std::string S;

#endif // C_H_INCLUDED

-

/* C.cpp */
#include "C.h"

#include <string>

// extern definition of global
const std::string S = "global string data";

-

/* TestClass.h */
#ifndef TESTCLASS_H_INCLUDED
#define TESTCLASS_H_INCLUDED

class TestClass
{
public:
    TestClass();
};

#endif // TESTCLASS_H_INCLUDED

-

/* TestClass.cpp */
#include "TestClass.h"

#include <iostream>

#include "C.h"  // for S global

TestClass::TestClass()
{
    std::cout << S << std::endl;  // this line crashes the program
}

Debugger messages about crash:

Program received signal SIGSEGV, Segmentation fault.
In std::basic_ostream<char, std::char_traits<char> >& std::operator<< <char, std::char_traits<char>, std::allocator<char> >(std::basic_ostream<char, std::char_traits<char> >&, std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) () ()
#1  0x004014f9 in TestClass::TestClass (this=0x4a0024 <j>) at E:\cpp\externconsttest\TestClass.cpp:9
E:\cpp\externconsttest\TestClass.cpp:9:117:beg:0x4014f9
At E:\cpp\externconsttest\TestClass.cpp:9
#1  0x004014f9 in TestClass::TestClass (this=0x4a0024 <j>) at E:\cpp\externconsttest\TestClass.cpp:9
E:\cpp\externconsttest\TestClass.cpp:9:117:beg:0x4014f9

This example crashes in operator<<, but it crashes on any reference to S no matter how it's referenced.

like image 995
beauxq Avatar asked Jun 15 '15 01:06

beauxq


2 Answers

I guess that it crashes because the global const std::string S is not initialized yet at the time when contructor of your TestClass is called. This is a general problem with global and static variables in C++: in general you do not know in which order global and static variables are initialized (actually they are initialized in order in which you pass object files to linker at linking stage - but that is not very helpful). There are a few different solutions to this problem. One of them is:

  1. Create a function with a static variable in its body that returns a reference to the variable (instead of just using global variable you would call that function). This is simmilar to the singleton design pattern:

    const std::string& get_my_string() { static const std::string S; return S; }

Then in your constructor:

TestClass::TestClass()
{
    std::cout << get_my_string() << std::endl;
}

Calling get_my_string will force the initialization of your static string only once (the first time the function is called) exactly at the time when you need it. Please notice that this example does not take threads into account (in a multi-threaded application you should synchronize the get_my_string() function to protect initialization of the static string).

I hope that helps.

By the way: you may get simmilar problems with your global TestClass j.

This way you are going to solve only half of the problem - initialization (you still don't know the order of destruction) - in most cases is it sufficient though.

Another option would be to create the string on heap (probably using simmilar approach as described above) - you just have to delete it at the time you know it is safe to do so.

like image 99
sirgeorge Avatar answered Sep 28 '22 20:09

sirgeorge


C++ does not provide symantics to control the ctor sequence for global scope objects in different compilation units. In addition, the next build could change the sequence.

The mechanism we used:

  • create global scope pointers initialized to null ptr.

And

  • After main starts but before any threads are started, new the objects in a rational order.

So, in this case ...

TestClass* j = nullptr; 

int main(...)
{
   // .. other init
   /* const std::string* */ 
   S = new std::string ("global string data");

   // now that S exists,   it is ok to intantiate:
   /* TestClass() */
   j = new TestClass;

   // much more stuff

   return(0);
}

Explicit control can always be made to work.

Another benefit - consistent start-up time.

A related gotcha - most of the pattern book patterns are not thread-safe. Combine multiple threads with no control of global scope ctors can be very difficult to debug.

like image 36
2785528 Avatar answered Sep 28 '22 18:09

2785528