创建构造函数引用全局变量的类的全局实例时,C++ 程序崩溃

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

本文关键字:C++ 崩溃 程序 实例 全局 构造函数 引用 全局变量 创建      更新时间:2023-10-16

我正在尝试创建一个类的全局实例,该类的构造函数引用全局变量。

程序编译没有错误。但是当它运行时,它会在全局变量的引用上崩溃。

如何在不让构造函数崩溃的情况下创建此类的全局实例?

这是我制作的SSCCE:

/* 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
}

有关崩溃的调试器消息:

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:cppexternconsttestTestClass.cpp:9
E:cppexternconsttestTestClass.cpp:9:117:beg:0x4014f9
At E:cppexternconsttestTestClass.cpp:9
#1  0x004014f9 in TestClass::TestClass (this=0x4a0024 <j>) at E:cppexternconsttestTestClass.cpp:9
E:cppexternconsttestTestClass.cpp:9:117:beg:0x4014f9

此示例在运算符<<中崩溃,但无论如何引用,它都会在对 S 的任何引用上崩溃。

我猜它崩溃是因为在调用TestClass的构造器时尚未初始化全局const std::string S。这是C++中全局变量和静态变量的普遍问题:通常您不知道全局变量和静态变量的初始化顺序(实际上它们是按照在链接阶段将对象文件传递给链接器的顺序初始化的 - 但这不是很有帮助)。此问题有几种不同的解决方案。其中之一是:

    创建一个函数,
  1. 其主体中有一个静态变量,该变量返回对该变量的引用(而不是仅使用全局变量,而是调用该函数)。这与单例设计模式类似:

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

然后在构造函数中:

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

调用 get_my_string 将只在您需要的时候强制初始化一次静态字符串(第一次调用函数时)。请注意,此示例不考虑线程(在多线程应用程序中,您应该同步get_my_string()函数以保护静态字符串的初始化)。

我希望这有所帮助。

顺便说一句:您的全球TestClass j可能会遇到类似的问题。

这样,您只能解决一半的问题 - 初始化(您仍然不知道销毁的顺序) - 在大多数情况下就足够了。

另一种选择是在堆上创建字符串(可能使用如上所述的模拟方法 - 您只需在知道这样做是安全的时候delete它。

C++不提供

语义来控制不同编译单元中全局作用域对象的 ctor 序列。 此外,下一个版本可能会更改顺序。

我们使用的机制:

  • 创建初始化为 NULL PTR 的全局作用域指针。

  • 在主启动之后但在启动任何线程之前,按合理顺序新建对象。

所以,在这种情况下...

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);
}

显式控制始终可以正常工作。

另一个好处 - 一致的启动时间。

一个相关的陷阱 - 大多数模式书模式都不是线程安全的。 组合多个线程而不控制全局作用域 ctor 可能很难调试。