当前位置:C++技术网 > 精选软件 > 静态全局变量放在头文件引发的怪异问题

静态全局变量放在头文件引发的怪异问题

更新时间:2016-08-22 13:00:03浏览次数:1+次

    最近遇到一个Bug,现象是这样的:静态全局变量g_data是一个vector容器,在一个静态函数d()中,会给这个容器添加一个元素,即调用push_pack函数来添加元素。那么问题来了,当这个静态函数d()执行完之后,退回调用静态函数的函数时,静态全局变量g_data数据凭空消失了。你没看错,就凭空消失了,看到的容器是空的。静态全局变量只在静态函数中添加元素,没有其他地方清空元素和删除元素的操作,但是,g_data里的东西就真的没有了。打断点跟踪,也总是能够复现,但是就是不知道哪里的问题。
    这种问题还是头一次遇见,真是打开眼界。再三检查代码的逻辑,没有找出问题。无奈之下,只有请同事帮忙检查,毕竟旁观者清,每一个人的代码风格都不一样,所以可能是我习惯了自己的代码风格,所以也很难自查。
    请来了同事,同事仔细看了之后,一开始也摸不着头脑,看代码也是没有逻辑问题。我们把其他几个静态全局变量,也监视了一下,静态函数退出时,也被清空了。后来加入了一个普通的int型静态全局变量,结果发现也被清除数据,结果为0,不是开始设置的100。
    后来同事说让我去掉全局变量的static关键字,也就是不要使用静态全局变量,看看有什么情况。结果去掉了static关键字之后,链接不通过,提示全局变量重定义,多处定义。通过错误提示,发现当前这个头文件被其他头文件引用了。
    当时因为疏忽,使用了static来解决的,却没有考虑到更多的后果。因为我的代码风格受到C#的风格的影响,把大量的函数定义放在了头文件,这样就不用跳来跳去的在头文件和源文件切换。更头痛的是,公司电脑有文件加密系统,对VS产生了干扰,导致VS的智能感知功能程序工作不正常,老是弹出错误提示框,几秒钟一次,让人没法工作。如果关闭智能感知,则VS的跳转功能也不能用了。在这样的情况下,更促使将代码都放在头文件,等开发完后再将代码恢复到源文件中。
    所以全局变量也放在头文件中。如果全局变量放在源文件中,头文件的函数则无法使用。最后,全局变量、类型定义、函数定义全部都在头文件完成。
    去掉static后,全局变量链接不通过,所以既然是代码风格怪异带来的一系列问题,去掉static也有问题,那就干脆将代码全部规范化,把代码定义和全局变量定义全部移到源文件中,这样全局变量不再需要static了,全局变量成为当前文件独享变量。测试后发现,静态函数执行完后,全局变量没有被清空了。问题成功解决。
    回过头来看代码,分析错误,其实都昭然若揭了。
    下面来总结分析,吸收经验,防止再出错。下面是一个代码结构图:
    一共涉及到两个线程,线程1和线程2。线程1有一个函数要调用静态函数d(),根据d()的执行结果做进一步的处理。d函数也就是回调函数。回调函数必须是静态函数,所以我们的d()也就是static修饰的静态函数。d()在文件2中。文件2是一个类的定义,其中,d()就是这个类的静态成员函数。静态成员函数和普通的C函数一样的,只是归在类中而已。而这个类主要在线程2下工作。这些静态成员函数作为回调函数,供线程1来调用。
    在文件2中,定义了静态的全局变量,d()函数负责给g_data添加数据,没有地方修改和删除数据。同时,文件2即a.h文件,被文件3(b.h)文件包含。文件3包含了文件2后,也就导入了所有的全局变量和函数声明。这样一来,静态全局变量就会被两个文件引用。在d()函数执行的时候,g_data第一次被操作,当d()函数执行完后,刚好文件3引用了a.h就导致g_data这个静态全局变量被清空。这里具体如何执行的清空,还不太清楚。因为涉及到两个线程,又有多个文件的关联,而且代码的里逻辑是比较复杂的,一时很难理清。不过问题被定位到这里,已经可以解决问题了。【这里的机制我并没有完全搞清楚,原始代码比较复杂,一下子捋不清楚,就不纠结了。只要正确的规范代码,就可以规避这样的问题。感谢李亚磊、寒霜、grep的指正和讨论交流】
    当我们在尝试将g_data的static去掉的时候,提示a.h和b.h重复定义了g_data,因为不是静态的变量,所以每一次包含都会促使创建一个全局变量g_data,这样就重复定义了两个相同的变量。而使用static规避了重定义的问题,实际上却埋有更大的隐患,因为共享一个静态变量后,也就相互对变量的数据产生共享操作。所以,在a.h文件中给g_data添加元素后,在b.h中又被初始化了。这样也就造成了数据丢失的问题。所以,一开始解决重定义的问题使用static就是错误的做法。
    至此,所有问题就说清楚了。下面总结一些正确的做法。
    头文件随时都可能被其他文件引用,所以在头文件中,不要定义全局变量,而是要放在源文件中。除非要故意让全局变量被其他文件使用,则可以在头文件中定义。而且在其他文件中,要使用extern关键字引用其他头文件定义的全局变量。此时只是将其他文件的全局变量引用过来而已,不会重新定义一个全局变量。此时用不用static则无关紧要。如果不想全局变量被其他文件使用,则放在源文件最好。
    如果头文件没有被其他文件引用,当然也不会引发本文所说的问题,但是不要心存侥幸,否则问题发生的时候,真的很难查。而且,如果你是故意让全局变量可以让其他文件共享的,那么最好要添加注释,便于今后维护,否则其他人可能会以为这是一个有问题有隐患的代码。
    另外,写代码跟踪调试即使是麻烦,也要做好规范,否则会带来更多问题,让代码工作不正常也很难调试。所以,这里也可以看到,规范代码的重要性。