【问题标题】:Non static global object vs global pointer to dynamic object非静态全局对象与指向动态对象的全局指针
【发布时间】:2012-04-30 06:42:03
【问题描述】:

出现以下无法解释的行为:

案例一:

a.cpp 编译为 .dll 库并在 main.cpp 的 main() 中使用

Bar b; 

//constr
Bar::Bar(){
  //... initialize members
}

//private library init
Bar::init(){ ...}

//public API init
bool lib_init(){
  b.init();
}

据我了解,这种方法可能会由于全局变量的未定义初始化行为而失败。

案例2:

a.cpp 编译为 .dll 库并在 main.cpp 的 main() 中使用

Bar* b; 

//constr
Bar::Bar(){
  //... initialize members
}

//private library init
Bar::init(){ ...}

//public API init
bool lib_init(){
  b = new Bar;
  b->init();
}

这一次它起作用了,当使用动态分配时。

案例 3(最令人惊讶)

a.cpp 编译为 .dll 库并在 main.cpp 的 main() 中使用

static Bar& getBarObj()
{
  static Bar g_objBar;
  return g_objBar;
}

//constr
Bar::Bar(){
  //... initialize members
}

//private library init
Bar::init(){ ...}

//public API init
bool lib_init(){
  getBarObj().init();
}

与案例 1 不同,其中 Bar obj 实例化可能未定义,在案例 3 中,它是“根据请求”使用的。然而,案例 3 提供了与案例 1 相同的行为。

我的问题是……谁能解释这里发生了什么? 一切都是用VC2008 Release模式构建的(这个项目没有Debug模式选项)

【问题讨论】:

  • 您看到了什么“无法解释的行为”?
  • 在 Bar constr 中,我有一个初始化循环。它的成员之一是 struct Foo 类型的固定大小数组,在“a.h”中定义。例如,循环不是迭代 SIZE_OF_ARRAY = 8 次,而是迭代 50K 次......
  • 请将其编辑到您的问题中,包括相关代码 - 在那篇(已经很长的)帖子中没有描述您的实际问题。

标签: c++ global-variables object-initializers non-static


【解决方案1】:

在 C++ 中未定义来自不同文件的全局变量的初始化顺序(您肯定有这种情况)。这意味着如果您依赖于在不同编译单元中的全局初始化期间构建的 Bar b,那么您的程序是未定义的。

为什么情况 3 应该起作用,因为它强制您使用一个函数来引用 Bar b,并且此函数保证 Bar b 将在此函数返回时构建。

如果您告诉我们您遇到了哪些确切的未定义行为,并提供最少量的代码进行分析,我们或许可以提供进一步的帮助。

附:您的 Bar 构造函数是否依赖于已构建的另一个全局变量?

【讨论】:

  • 在啊,除了 Bar 类定义,我有一个 Foo 结构。 Bar 的成员之一是一个固定大小的 Foo 数组:
  • 难道SIZE_OF_ARRAY 是一个在你到达Bar 构造函数时还没有初始化的全局变量?只有这样才能导致 50k 次迭代。可以在初始化Bar之前打印SIZE_OF_ARRAY的值以确保它已经初始化吗?
【解决方案2】:

情况 1 可能会失败,因为 b 的构造本质上取决于链接顺序(不受 C++ 规范控制:它是 C++ 未定义的行为,但行为可以由链接器定义!) 在任何情况下,一旦您调用 lib_init b 被授予构造为 lib_initb 留在同一个模块中。

案例 3 在需要时构造 b,并将在终止时以与所有其他静态和全局对象相反的构造顺序销毁它。 如果Bar::Bar() 依次调用foo_init() 内部有一个静态Foo,这可能会给旧编译器带来一些问题:您需要BarBar 需要Foo,但Bar 是在Foo 之前创建的和FooBar 之前销毁,那个垫子仍然需要它。 自 2003 年规范以来,这一事实应该不再发生。需要在构造完成后进行销毁调度(因此Foo ctor 在Bar ctor 之前完成,因此 bar dtor 将在Foo dtor 之前被调用。

案例 2 是“丑陋的”:像案例 3 一样工作(按需创建),但有两个问题:

  • 指针应该是静态的,只是为了避免在多次调用 lib_init() 的情况下创建多个对象...
  • 谁会摧毁Bar? Os 将在终止时收回内存,但不会调用 dtor。

案例 2 可能更好地定义为

void lib_init()
{
    static std::unique_ptr<Bar> p(new Bar);
    p->init();
}

但这会让它像案例 3。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-03-11
    • 2021-09-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-24
    • 1970-01-01
    相关资源
    最近更新 更多