HALCON 控制变量没有被初始化_C++全局变量初始化

c++ 全局变量初始化的一点总结

对于C语言的全局和静态变量,不管是否被初始化,其内存空间都是全局的;如果初始化,那么初始化发生在任何代码执行之前,属于编译期初始化。由于内置变量无须资源释放操作,仅需要回收内存空间,因此程序结束后全局内存空间被一起回收,不存在变量依赖问题,没有任何代码会再被执行!

C++引入了对象,这给全局变量的管理带领新的麻烦。C++的对象必须有构造函数生成,并最终执行析构操作。由于构造和析构并非分配内存那么简单,可以说相当复杂,因此何时执行全局或静态对象(C++)的构造和析构呢?这需要执行相关代码,无法在编译期完成,因此C++标准规定:全局或静态对象当且仅当对象首次用到时才进行构造,并通过atexit()来管理对象的生命期,在程序结束之后(如调用exit,main),按FILO顺序调用相应的析构操作!

全局变量、文件域的静态变量和类的静态成员变量在main执行之前的静态初始化过程中分配内存并初始化;局部静态变量(一般为函数内的静态变量)在第一次使用时分配内存并初始化。这里的变量包含内置数据类型和自定义类型的对象。

函数内的静态对象,初始化是在第一次运行到那里的时候才初始化的。假设有一个函数testFun,大约是下面这样

void testFun()
{
static std::string str("Test string");
}

那么函数里面那个str,要第一次运行testFun的时候才会被初始化。

初始化的顺序

对于出现在同一个编译单元内的全局变量来说,它们初始化的顺序与他们声明的顺序是一致的(销毁的顺序则反过来),而对于不同编译单元间的全局变量,c++ 标准并没有明确规定它们之间的初始化(销毁)顺序应该怎样,因此实现上完全由编译器自己决定,一个比较普遍的认识是:不同编译单元间的全局变量的初始化顺序是不固定的,哪怕对同一个编译器,同一份代码来说,任意两次编译的结果都有可能不一样[1]。

因此,一个很自然的问题就是,如果不同编译单元间的全局变量相互引用了怎么办?

当然,最好的解决方法是尽可能的避免这种情况.

几个技巧

好吧,我承认总有那么一些特殊的情况,是需要我们来处理这种在全局变量的初始化函数里竟然引用了别的地方的全局变量的情况,比如说在全局变量的初始化函数里调用了 cout, cerr 等(假设是用来打 log, 注意 cout 是标准库里定义的一个全局变量)[2],那么标准库是怎样保证 cout 在被使用前就被初始化了呢? 有如下几个技巧可以介绍一下。

Construct On First Use

该做法是把对全局变量的引用改为函数调用,然后把全局变量改为函数内的静态变量:

int get_global_x()
{
   static X x;
   return x.Value();
}

这个方法可以解决全局变量未初始化就被引用的问题,但还有另一个对称的问题它却没法解决,函数内的静态变量也属于 variables with static storage, 它们析构的顺序在不同的编译单元间也是不确定的,因此上面的方法虽然必然能保证 x 的初始化先于其被使用,但却没法妥善处理,如果 x 析构了 get_global_x() 还被调用这种可能发生的情况。

一个改进的做法是把静态变量改为如下的静态指针:

int get_global_x()
{
   static X* x = new X;
   return x->Value();
}

这个改进可以解决前面提到的 x 析构后被调用的问题,但同时却也引入了另一个问题: x 永远都不会析构了,内存泄漏还算小问题或者说不算问题,但如果 x 的析构函数还有事情要做,如写文件清理垃圾什么的,此时如果对象不析构,显然程序的正确性都无法保证.

这个问题在 gcc c++ 的标准库里也没有得到解决,有兴趣的可以看看这个讨论。

--more

Storage duration

All objects in a program have one of the following storage durations:

  • automatic storage duration. The storage for the object is allocated at the beginning of the enclosing code block and deallocated at the end. All local objects have this storage duration, except those declared static, extern or thread_local.
  • static storage duration. The storage for the object is allocated when the program begins and deallocated when the program ends. Only one instance of the object exists. All objects declared at namespace scope (including global namespace) have this storage duration, plus those declared with static or extern.
  • thread storage duration. The storage for the object is allocated when the thread begins and deallocated when the thread ends. Each thread has its own instance of the object. Only objects declared thread_local have this storage duration. thread_local can appear together with static or extern to adjust linkage.

(since C++11)

  • dynamic storage duration. The storage for the object is allocated and deallocated per request by using dynamic memory allocation functions.
constexpr char kFoxSays[] = "Abay-ba-da bum-bum bay-do";

初始化没有顺序。一个全局变量可能指到另一个全局变量。但那个全局变量可能还有初始化或者已经end了。

各全局变量的析构函数是顺序调用的,与调用构造函数的顺序是相反的。这就保证做到“先构造的全局类变量后析构。”

Dynamic initialization is not ordered across translation units, and neither is destruction (except that destruction happens in reverse order of initialization).

When one initialization refers to another variable with static storage duration, it is possible that this causes an object to be accessed before its lifetime has begun (or after its lifetime has ended). Moreover, when a program starts threads that are not joined at exit, those threads may attempt to access objects after their lifetime has ended if their destructor has already run.