【问题标题】:Can I change static variable initialization order in C++?我可以更改 C++ 中的静态变量初始化顺序吗?
【发布时间】:2017-04-24 15:11:07
【问题描述】:

我在 Visual Studio 2015 sp3 中使用 C++。 由

#pragma init_seg(compiler)

,我先初始化一些静态变量(用于内存管理)。 https://msdn.microsoft.com/en-us/library/7977wcck.aspx

但是,有

#pragma init_seg(compiler)

在 wcerr.cpp(Microsoft Visual Studio 14.0\VC\crt\src\stl\wcerr.cpp) 中,所以这些对象在我的对象之前初始化。

我可以通过任何编译/链接选项强制我的对象在 wcerr.cpp 对象之前首先初始化吗?

【问题讨论】:

  • 为什么顺序很重要?
  • @Cheersandhth.-Alf 因为我的对象初始化了自定义内存系统,如果其他一些对象在我的对象之前调用 new ,就会出错。 :(
  • 所以实际的问题是 - how to override a memory allocator in MSVC

标签: c++ visual-studio visual-studio-2015 pragma static-variables


【解决方案1】:

编辑:我认为您需要的解决方案是使用另一个 STL 实现,如果您需要使用 MSVC 2015 并且还避免重新实现您当前的内存管理黑客(很抱歉这么称呼它,但听起来就是这样,但又一次我已经使用自己的一些技巧来使用 MSVC)。

我从未使用过不包含在开发环境中的 STL 实现,但这是可能的。将包含和库路径设置为替代 STL 实现应该是一件简单的事情。你可以先试试STLPort

否则,在最后一次编辑之前,我对您的问题的原始建议:

编译单元(cpp 文件)中的静态初始化顺序是进行声明的顺序。编译单元之间的静态初始化顺序未定义。不要使用静态初始化来解决这个问题。

RustyX 的评论链接到 Overriding memory allocator in MSVC++ 似乎为您指明了正确的方向。

编辑:看起来您正在寻找的答案可能在这里:How to properly replace global new & delete operators。请注意,这两个答案都是相关的,您需要替换 newdeletenew[]delete[]。由于这是在链接期间应用的,显然 Windows 不会将这些用于您加载的 DLL(请参阅第一个答案中的 cmets)。

【讨论】:

  • 因为内存管理代码不只是我的,而是在框架中。在 MSVC 2015 之前,它总是在 wcerr 之前构建。即使在 MSVC 2015 中,在 Debug build 中,我的静态对象也是在 wcerr 之前构建的。只有在 Release 构建中,对象的构建顺序才会改变。
  • 这听起来像是一个前向兼容性问题。您在以前的 MSVC 版本中很幸运,我会说不要使用 MSVC 2015。您使用的特殊编译器指令不应该依赖于我所说的静态初始化顺序的原因。编译器(和库)发生变化。我认为使用 MSVC 2015 的唯一选择是不使用 Microsoft 的 STL,或者可能修改它并构建自己的(但我认为它没有为此目的获得许可)。例如,Boost 有一个著名的 STL 实现。
【解决方案2】:

其中一种解决方案是尝试将静态变量包装到静态函数中:

static type& My_static_obj() {
    static type my_static_obj_;
    return my_static_obj_;
}

它看起来像一个简单的 Singleton 类型,并调用 Construct On First Use Idiom。由于标准(C++ 11 及更高版本),它保证被初始化一次(甚至是原子的!),并且在它的 c-tor 内部,这样一个对象可以访问其他“静态”变量,所以,如果没有循环变量之间的依赖关系,初始化的顺序会被严格定义。

如需更多信息,请参阅this question 以及此Construct On First Use Idiom 的其他描述。

【讨论】:

  • 'Construct On First Use Idiom' 是很好的信息。谢谢。但是我必须在 Microsoft Visual Studio 14.0\VC\crt\src\stl\wcerr.cpp 之前构造对象。 wcerr 可以用这个成语吗?
  • Standard 说它(可能)可以通过包含<iostream> 你的对象的初始化(第二段,它导致@987654326 @initialization),也许你必须为wcerr 做前向声明。或者您可以根据您的目的明确扩展std::ios_base::Init c-tor,但这是一个肮脏的黑客。尝试让我们知道。再来一次:一般来说,如前所述,顺序没有定义,似乎 VS 和 Win 会在任何用户的代码之前尝试初始化它们的实用程序。
  • 为了最终的正确性而挑剔:答案代码中的每个“类型”都应该是“类型 const”,除非 my_static_obj_ 实例是可修改的。如果它打算是可修改的,那么应该有 const 和缺少 const 重载的 My_static_obj 函数,以便编译器可以在其使用点选择适当的只读与可修改。对于右值引用重载也是如此。 (如果 my_static_obj_ 被定位在一个固定地址作为 DMA 到 IC 的寄存器,例如在设备驱动程序中,那么同样用于易失性重载。)
【解决方案3】:

在这种情况下,nifty counter 成语可能会以某种方式帮助您:

确保非本地静态对象在第一次使用前被初始化,并在对象最后一次使用后销毁。

它的动机很明确:

当静态对象使用其他静态对象时,初始化问题变得更加复杂。如果静态对象具有非平凡初始化,则必须在使用前对其进行初始化。跨编译单元的静态对象的初始化顺序没有明确定义。分布在多个编译单元中的多个静态对象可能正在使用单个静态对象。因此,必须在使用前对其进行初始化。一个例子是 std::cout,它通常被许多其他静态对象使用。

从上面的链接页面直接复制和粘贴示例是值得的:

Stream.h

#ifndef STREAM_H
#define STREAM_H

struct Stream {
  Stream ();
  ~Stream ();
};
extern Stream& stream; // global stream object

static struct StreamInitializer {
  StreamInitializer ();
  ~StreamInitializer ();
} streamInitializer; // static initializer for every translation unit

#endif // STREAM_H

Stream.cpp

#include "Stream.h"

#include <new>         // placement new
#include <type_traits> // aligned_storage

static int nifty_counter; // zero initialized at load time
static typename std::aligned_storage<sizeof (Stream), alignof (Stream)>::type
  stream_buf; // memory for the stream object
Stream& stream = reinterpret_cast<Stream&> (stream_buf);

Stream::Stream ()
{
  // initialize things
}
Stream::~Stream ()
{
  // clean-up
} 

StreamInitializer::StreamInitializer ()
{
  if (nifty_counter++ == 0) new (&stream) Stream (); // placement new
}
StreamInitializer::~StreamInitializer ()
{
  if (--nifty_counter == 0) (&stream)->~Stream ();
}

必须先包含 Stream 类的头文件,然后才能对 Stream 对象调用任何成员函数。 StreamInitializer 类的一个实例包含在每个编译单元中。对 Stream 对象的任何使用都遵循包含标头,这确保在使用 Stream 对象之前调用初始化器对象的构造函数。

有关详细信息,请参阅上面的链接。

【讨论】:

    猜你喜欢
    • 2015-06-01
    • 2010-09-17
    • 2018-12-19
    • 1970-01-01
    • 2021-07-12
    • 1970-01-01
    相关资源
    最近更新 更多