c++ 全局变量初始化的一点总结
注意:本文所说的全局变量指的是 variables with static storage,措词来自 c++ 的语言标准文档。
什么时候初始化
根据 C++ 标准,全局变量的初始化要在 main 函数执行前完成,常识无疑,但是这个说法有点含糊,main 函数执行前到底具体是什么时候呢?是编译时还是运行时?答案是既有编译时,也可能会有运行时(seriously), 从语言的层面来说,全局变量的初始化可以划分为以下两个阶段(c++11 N3690 3.6.2):
static initialization: 静态初始化指的是用常量来对变量进行初始化,主要包括 zero initialization 和 const initialization,静态初始化在程序加载的过程中完成,对简单类型(内建类型,POD等)来说,从具体实现上看,zero initialization 的变量会被保存在 bss 段,const initialization 的变量则放在 data 段内,程序加载即可完成初始化,这和 c 语言里的全局变量初始化基本是一致的。
dynamic initialization:动态初始化主要是指需要经过函数调用才能完成的初始化,比如说:
int a = foo()
,或者是复杂类型(类)的初始化(需要调用构造函数)等。这些变量的初始化会在 main 函数执行前由运行时调用相应的代码从而得以进行(函数内的 static 变量除外)。
需要明确的是:静态初始化执行先于动态初始化! 只有当所有静态初始化执行完毕,动态初始化才会执行。显然,这样的设计是很直观的,能静态初始化的变量,它的初始值都是在编译时就能确定,因此可以直接 hard code 到生成的代码里,而动态初始化需要在运行时执行相应的动作才能进行,因此,静态初始化先于动态初始化是必然的。
初始化的顺序
对于出现在同一个编译单元内的全局变量来说,它们初始化的顺序与他们声明的顺序是一致的(销毁的顺序则反过来),而对于不同编译单元间的全局变量,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 的析构函数还有事情要做,如写文件清理垃圾什么的,此时如果对象不析构,显然程序的正确性都无法保证。
Nifty counter.
完美一点的解决方案是 Nifty counter, 现在 GCC 采用的就是这个做法[3][7]。假设现在需要被别处引用的全局变量为 x, Nifty counter 的原理是通过头文件引用,在所有需要引用 x 的地方都增加一个 static 全局变量,然后在该 static 变量的构造函数里初始化我们所需要引用的全局变量 x,在其析构函数里再清理 x,示例如下:
// global.h#ifndef _global_h_
#define _global_h_extern X x;class initializer
{public:initializer(){if (s_counter_++ == 0) init();}~initializer(){if (--s_counter_ == 0) clean();}private:void init();void clean();static int s_counter_;
};static initializer s_init_val;#endif
相应的 cpp 文件:
// global.cpp#include "global.h"static X x;int initializer::s_counter_ = 0;void initializer::init()
{new(&x) X;
}void initializer::clean()
{(&x)->~X();
}
代码比较直白,所有需要引用 x 的地方都需要引用 global.h
这个头文件,而一旦引入了该头文件,就一定会引入 initializer 类型的一个静态变量 s_init_val
, 因此虽然不同编译单元间的初始化顺序不确定,但他们都肯定包含有 s_init_val,因此我们可以在 s_init_val 的构造函数里加入对 x 的初始化操作,只有在第一个 s_init_val 被构造时才初始化 x 变量,这可以通过 initializer 的静态成员变量来实现,因为 s_counter_ 的初始化是静态初始化,能保证在程序加载后就完成了。
初始化 x 用到了 placement new 的技巧,至于析构,那就是简单粗暴地直接调用析构函数了,这一段代码里的技巧也许有些难看,但都是合法的,当然,同时还有些问题待解决:
首先,因为 x 是复杂类型的变量,它有自己的构造函数,init() 函数初始化 x 之后,程序初始化 x 所在的编译单元时,x 的构造函数还会被再调用一次,同理 x 析构函数也会被调用两次,这显然很容易引起问题,解决的方法是把 x 改为引用:
// global.cpp#include "global.h"// need to ensure memory alignment??
static char g_dummy[sizeof(X)];static X& x = reinterpret_cast<X&>(g_dummy);int initializer::s_counter_ = 0;void initializer::init()
{new(&x) X;
}void initializer::clean()
{(&x)->~X();
}
其中 static X& x = reinterpret_cast<X&>(g_dummy);
这一行是静态初始化,因为 g_dummy 是编译时就确定了的(引用是简单类型且以常量为初始值),而 x 只是一个强制转化而来的引用,编译器不会生成调用 x 构造函数和析构函数的代码。通过上面的修改,这个方案已经比较完美了,但遗憾的是它也不是 100% 正确的,这个方案能正确工作的前提是:所有引用 x 的地方都会 include 头文件 global.h,但如果某一个全局变量 y 的初始化函数里没有直接引用 x, 而是间接调用了另一个函数 foo,再通过 foo 引用了 x,此时就可能出错了,因为 y 所在的编译单元里可能并没有直接引用 x,因此很有可能就没有 include 头文件 global.h,那么 y 的初始化就很有可能发生在 x 之前。。。
这个问题在 gcc c++ 的标准库里也没有得到解决,有兴趣的可以看看这个讨论。
[参考]
[1] http://isocpp.org/wiki/faq/ctors#static-init-order
[2] https://gcc.gnu.org/onlinedocs/libstdc++/manual/io.html#std.io.objects
[3] https://gcc.gnu.org/onlinedocs/libstdc++/libstdc++-html-USERS-3.4/ios__init_8cc-source.html
[4] https://social.msdn.microsoft.com/Forums/vstudio/en-US/637a4c27-3e30-4b88-b36d-b5b720cf0d04/why-are-cout-cin-initialized-once-and-only-once-given-the-scheme-below-in-the-iostream?forum=vclanguage
[5] http://www.petebecker.com/js/js199905.html
[6] http://blogs.msdn.com/b/ce_base/archive/2008/06/02/dynamic-initialization-of-variables.aspx
[7] http://cs.brown.edu/people/jwicks/libstdc++/html_user/globals__io_8cc-source.html
转载于:https://www.cnblogs.com/catch/p/4314256.html
c++ 全局变量初始化的一点总结相关推荐
- 全局对象_C++全局变量初始化
c++ 全局变量初始化的一点总结 对于C语言的全局和静态变量,不管是否被初始化,其内存空间都是全局的:如果初始化,那么初始化发生在任何代码执行之前,属于编译期初始化.由于内置变量无须资源释放操作,仅需 ...
- 全局变量初始化顺序探究
缘起 我在上一篇文章--<调试实战 -- dll 加载失败之全局变量初始化篇>中,跟大家分享了一个由于全局变量初始化顺序导致的 dll 加载失败的例子.感兴趣的小伙伴儿可以点击阅读. 虽然 ...
- 调试实战 —— dll 加载失败之全局变量初始化篇
前言 最近项目里总是遇到 dll 加载不上的问题,原因各种各样.今天先总结一个虽然不是项目中实际遇到的问题,但是却非常经典的问题.其它几种问题,后续慢慢总结. 示例代码包含一个 exe 工程,两个 d ...
- 关于对于c语言全局变量初始化问题
1: 强弱符号学说: 在C语言里,全局变量如果不初始化的话,默认为0,也就是说在全局空间里: int x =0; 跟 int x; 的效果看起来是一样的.但其实这里面的差别很大,强烈建议大家所有的全局 ...
- C++全局变量初始化的顺序
虽然一直强调不要用全局变量.但是对于特殊的应用场合,还是有全局变量的使用(如某些多进程.多线程的共享资源),我们希望在首次运行(载入)时,系统能够帮助我们进行一些必要的初始化. If a progra ...
- java全局变量初始化_为什么 Java 中全局变量不一定初始化,局部变量必须初始化?...
前几天,有个大佬考了我一个问题:为什么类的成员变量不需要指定值就可以自动赋予初始值,而方法内的局部变量就必须指定初始值呢? 说实话,这个问题确实难倒我了,之后我在网上一顿操作,终于把这个问题弄得稍微明 ...
- leetcode全局变量初始化问题,报错 AddressSanitizer: heap-buffer-overflow on address
结论:leetcode中,全局变量需在函数中做初始化.因为:leetcode"提交"的代码,全局变量定义时做的初始化,可能并没有成功. 简单举例: // 报错 int a = 0; ...
- 关于CRM库存初始化的一点小总结
日前,有客户在使用CRM管理库存的过程中反馈了一个关于库存台账的bug(姑且称为bug吧).例如:12月份通过进货单,采购了10台笔记本.完成产品入库.那么笔记本库存台账: 期初库存数量为0.期间入库 ...
- [ARM-assembly]-全局变量/静态全局变量/初始化/未初始化变量的存放位置分析
我们从汇编来看,各种变量在内存中的存放位置 1.使用交叉编译器将C语言转换成汇编 写一个C语言程序: static int x; static int y = 10; int z; int w = 2 ...
最新文章
- 不能查看隐藏文件夹的对策
- 15.selenium_case01
- Codis集群的搭建与使用
- HBuilderX代码设置断点或者书签
- 信息学奥赛一本通(C++)在线评测系统——基础(一)C++语言——1109:开关灯
- 在javascript当中发现了一个没有调用者的方法。
- 本科计算机专业学物理力学吗,力学专业
- 几行Python代码生成饭店营业额模拟数据并保存为CSV文件
- windows10下搭建spark平台
- 《Elementary Methods in Number Theory》勘误
- 航空三字代码表_航空公司二字及三字代码表
- 央视《家有妙招》整理版,共250招,值得永远收藏
- 计算机网页外文文献图书,免费外文文献网站.doc
- ThreeJS —— 机房Demo(二)
- 【Bash百宝箱】Makefile快速入门
- OTT发展背后的融合通讯
- 关于前端在vue中实现‘距离某个时间点的倒计时’问题:设置了间隔计算时间,刚开始有停顿。
- Kudo介绍 + Spark\Python\Scala开发Kudu应用程序
- Unity 调用摄像头拍照保存
- 推荐两个长宽数据互换函数pivot_longer和pivot_wider
热门文章
- 柳传志大声对iphone说:这是在中国!
- IPVS-DR+keepalived实现高可用负载均衡集群
- GridView,Repeater分页控件:WebPager(开源)
- WPF/E CTP Quick Start - 第三部分:Canvas对象(翻译)
- 科普:BCH能够买什么?如何使用BCH买东西?
- 比特币官网管理者Cobra认可BCH支付属性
- Could not find action or result
- [SHOI2007]园丁的烦恼
- (1)虚拟机管理——在微软云Azure新门户创建虚拟机
- Amazon Aurora 升级, 兼容 PostgreSQL