如何查找和解决全局变量之间的依赖关系

时间:2011-03-25 15:29:49

标签: c++ global-variables

我有一个应用程序,[不幸的是]包含了一些全局变量。最近一次启动崩溃导致我进入以下结构:

FILE1.CPP:
ClassX globalVariableX;

FILE2.CPP
ClassY globalVariableY;

不幸的是,Y类的构造函数使用的是使用globalVariableX的代码。

直到现在一切都很顺利,因为(巧合的是)FILE1.OBJ在FILE2.OBJ之前被链接,这意味着globalVariableX在globalVariableY之前被实例化。

上周,其他文件中完全不相关的更改导致链接器在FILE1.OBJ之前链接到FILE2.OBJ中。现在globalVariableY首先被实例化,它的构造函数间接引用globalVariableX,并且因为globalVariableX尚未实例化而崩溃。

我知道我应该尽可能地摆脱所有的全局变量(请不要就此开始辩论)。

但是否有可用的工具可以帮助我查找全局变量之间的依赖关系?

或者是否有任何技巧可以在运行时看到,如果有依赖我应该摆脱(我正在考虑为全局变量引入一个基类,我可以在其中记录全局构造变量,但这可能相当一些工作)。还有其他建议吗?

修改 你的所有答案都是关于如何预防这些令人讨厌的问题的非常好的建议。但我实际上是在寻找一种方法来找到这些依赖关系,而不是删除所有全局变量(或用其他结构替换它们)。有关找到这些依赖关系的工具的任何想法吗?

3 个答案:

答案 0 :(得分:2)

通常的解决方案依赖于首次使用时的初始化。

在C ++中,您可以使用本地静态(在函数中)来获取此行为。在C ++ 0x中(但已在主要编译器中实现),这甚至可以保证是线程安全的(至少是初始化)。

ClassX& GetClassX() {
  static ClassX X; return X;
}

ClassY& GetClassY() {
  static ClassY Y; return Y;
}

但仍有一个问题:这样的方案不会检测循环引用。它可能类似于一个疯狂的递归,从而打击你的堆栈;)

答案 1 :(得分:1)

看看这里:http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.14

  

[10.14]什么是“静态初始化顺序惨败”?

     

使程序崩溃的一种微妙方法。

     

静态初始化命令fiasco   是一个非常微妙和普遍的   误解了C ++的一个方面。   不幸的是,它很难被发现    - 错误经常发生在main()之前   开始。

     

简而言之,假设您有两个静态   存在于中的对象xy   单独的源文件,比如说x.cpp和   y.cpp。进一步假设   y对象的初始化   (通常是y对象的构造函数)   在x对象上调用一些方法。

     

就是这样。就这么简单。

     

悲剧在于你有50%-50%   死亡的机会。如果编译   x.cpp的单位恰好得到了   首先初始化,一切都很好。但如果   获得y.cpp的编译单元   首先初始化,然后是y   初始化将在x之前运行   初始化,你敬酒。   例如,y的构造函数可以调用   x对象上的方法,x object hasn't yet been constructed.

     

我听说他们正在招聘   麦当劳。享受你的新工作翻转   汉堡包。

     

如果你觉得玩“令人兴奋”   俄罗斯轮盘与现场轮   一半的房间,你可以停下来   在这里读另一方面,如果你   喜欢提高你的机会   通过预防灾难生存   系统的方式,你可能想要   阅读下一个FAQ。

     

注意:静态初始化顺序   在某些情况下,惨败也可以适用   内置/内在类型。

     

[10.15]如何防止“静态初始化命令惨败”?

     

使用“首次使用时构造”   成语,这只是意味着包装你的   函数内的静态对象。

     

例如,假设您有两个   班级,弗雷德和巴尼。有一个   全局Fred对象叫做x,和a   全球Barney对象叫做y。   巴尼的构造函数调用了   x对象上的goBowling()方法。   文件x.cpp定义了x对象:

 // File x.cpp
 #include "Fred.h"
 Fred x;
     

文件y.cpp定义y对象:

 // File y.cpp
 #include "Barney.h"
 Barney y;
     

为了完整性,巴尼   构造函数可能看起来像   这样:

 // File Barney.cpp
 #include "Barney.h"

 Barney::Barney()
 {
   ...
   x.goBowling();
   ...
 }
     

如上所述,灾难   如果y在x之前构造,则会发生   从那时起50%的时间都会发生   他们在不同的源文件中。

     

有很多解决方案   问题,但很简单   完全便携的解决方案是   替换全局Fred对象x,   具有全局函数x(),即   通过引用返回Fred对象。

 // File x.cpp

 #include "Fred.h"

 Fred& x()
 {
   static Fred* ans = new Fred();
   return *ans;
 }
     

因为静态本地对象是   构建了第一次控制   流过他们的声明(仅),   上面新的Fred()声明会   只发生一次:第一次x()   叫做。每次后续通话都会   返回相同的Fred对象(一个   ans指出。然后你所做的就是   将x的用法更改为x():

 // File Barney.cpp
 #include "Barney.h"

 Barney::Barney()
 {
   ...
   x().goBowling();
   ...
 }
     

这称为Construct On First   使用成语,因为它只是这样:   构造全局Fred对象   首次使用时。

     

这种方法的缺点是   Fred对象永远不会被破坏。   还有另一种技术   回答这个问题,但它需要   小心使用,因为它创造了   另一种可能性(同样令人讨厌)   问题

     

注意:静态初始化顺序   在某些情况下,惨败也可以适用   内置/内在类型。

(来自C ++ FAQ,http://www.parashift.com/c++-faq/

答案 2 :(得分:1)

您可能希望通过使用构造变量的应用程序构建器完全避免全局变量,并将其传递给依赖变量以使依赖项清晰。