别怕Linux编程

Google C++编程规范 – 第二十八条 -《静态变量和全局变量》

本原创文章属于《Linux大棚》博客。

博客地址为http://roclinux.cn

文章作者为roc wu

==

【规范】

不允许声明类的静态变量和类的全局变量,这是因为这样会引起构造函数和析构函数执行顺序的混乱。但如果使用了constexpr:来修饰,那么就允许使用,这是因为这样不会引起动态初始化或动态析构。

【详解】

全局变量、静态变量、静态类成员变量、函数内静态变量,都称作具有静态生存周期的变量,这类变量必须是POD,即Plain Old Data,直译成中文是“普通的古老的数据类型”。而POD仅包括整型(int)、字符型(char)、浮点型(float)、指针类型(pointer)、数组类型(array)和结构体类型(struct)。

类构造函数和初始化函数对于静态变量的处理顺序在C++标准中是没有明确定义的,甚至每次构建的顺序都会不同,这就会导致非常难以发现的bug。因此,我们不仅禁止使用全局类类型,还禁止通过函数返回值来给静态POD变量初始化,除非这个函数自身不依赖于任何全局变量,比如getenv()、getpid()等。

类似的,一个程序无论是通过main函数return方式,还是调用exit()方式终止,程序中的全局变量和静态变量都会被消灭。而对于全局变量和静态变量的析构,则与其被构造的顺序完全相反。但不幸的是,其构造的顺序是不确定的,因此析构的顺序也会是不确定的。例如,在程序的尾声阶段,某一个线程希望访问某一静态变量,但是其可能已经被程序析构了,这就会导致访问失败。又例如,某一个静态字符串变量已经被析构了,而指向此字符串的某个指针却还在被使用。

那么如何解决上述问题呢?

解决上述问题的一个方法是在程序结束时,调用quick_exit(),而不是调用exit()。两者的区别在于,quick_exit()不会触发析构,也不会触发那些通过atexit()注册的钩子。如果你偏偏想在调用quick_exit()退出时调用某个钩子,比如要将日志刷到磁盘上,那你可以使用at_quick_exit()来注册这个钩子。补充一句,如果你希望某个钩子在exit()和quick_exit()时都能被调用,那你就要同时通过at_quick_exit()和at_exit()来注册。

综上,我们只允许POD被作为静态变量来使用,诸如vector或string这类类型都是绝对不允许的,前者建议替换成C数组,后者则建议替换成const char []。

如果你确实需要一个类类型的静态变量或全局变量,可以考虑在你的main函数内部或通过pthread_once函数来初始化一个永不回收的指针。要注意的是,你声明的必须是一个原始指针,而不能是一些高级指针(smart pointer),这是因为高级指针有会引入析构顺序的问题。

谢谢!

发表您的评论

请您放心,您的信息会被严格保密。必填项已标识 *