使用init_seg时如何为三个C++对象命名sections/groups?

How to name sections/groups for three C++ objects when using init_seg?

我正在使用 init_seg 来控制三个 C++ class 对象的创建。每个对象都在不同的源 file/translation 单元中。调试显示在 CRT 初始化期间正在按预期创建对象。

正在按其源文件的字母顺序初始化对象。我想更改它,因为它不太正确。我在 init_seg 上访问了 MSDN 的页面,它指出使用是:

#pragma init_seg({ compiler | lib | user | "section-name" [, func-name]} )

看来 libsection-name 的使用是相互排斥的,所以我不清楚如何使用 init_seg(lib) 并提供 section/group 名称来获取按字母排序的权利。

当我尝试使用按字母顺序排列的字符串来控制顺序时:

#pragma init_seg(lib, "01")

它会导致警告,我猜这意味着事情不会按预期工作:

warning C4081: expected ')'; found ','

当我尝试直接使用 ".CRT$XCB"".CRT[=23=]1"".CRT$XCB001"(以及其他利用字母顺序的变体)直接插入 CRT 启动代码时:

#pragma init_seg(".CRT$XCB")

它导致另一个警告,我猜这意味着事情不会按预期工作:

warning C4075: initializers put in unrecognized initialization area

我在 Stack Overflow 上发现了一个关于它的问题,但是 Wayback Machine 上的 answer was a guess and it does not cover multiple translation units. I also found an archive of KB104248 也没有太大帮助,因为它只显示了 compiler、[=19= 的用法] 和 user.

所以我的问题是,如何利用 init_seg 来控制三个不同源文件中三个对象的精确创建顺序?

如果使用自定义栏目名称,则需要使用#pragma sectionhttps://msdn.microsoft.com/en-us/library/50bewfwa.aspx)指定栏目属性

#pragma section("foo",long,read,write)
#pragma init_seg("foo")

您可以通过在美元符号后添加后缀来确保来自多个文件的自定义段是有序的。

// tu1.cpp
#pragma section("foo",long,read,write)
#pragma init_seg("foo")

// tu2.cpp
#pragma section("foo",long,read,write)
#pragma init_seg("foo")

tu1.cpp 的数据现在将在 tu2.cpp

的数据之前

您可以通过向 CRT 段添加后缀来对与 C 运行时库相关的内容进行排序

// tu1.cpp
#pragma section(".CRT$XCU1",long,read,write)
#pragma init_seg("foo")

// tu2.cpp
#pragma section(".CRT$XCU2",long,read,write)
#pragma init_seg("foo")

TU1 现在在 TU2 之前,并与其他数据分组

这是我在 XP 和 VS2002/VS2003、Vista 和 VS2005/VS2008、Windows 7 和 VS2008/VS2010、Windows 8 和 VS2010/VS2012/VS2013,和 Windows 10 使用 VS2015。 #pragma_init(<name>) 自 VC++ 1.0 天后可用。 MS 没有发布太多关于它的信息,但我们从 VC++1.0 (archived KB104248) through VS2017.

中知道它的文档
  1. #pragma init_seg(lib)几近完美。但是,目标文件在 VS2008 和更早版本中按字母顺序排列,因此初始化顺序是 a-b-c(不需要)而不是 c-b-a(需要)。它在 VS2010 及更高版本上没问题。不明显的是 vcproj 文件中的顺序与 c-b-a 完全相同。

  2. #pragma init_seg(".CRT$XCB-0NN") 似乎有效。我们的 std::strings STRING_ASTRING_B 是早期创建的(并且对象的顺序正确),但是 STRING_B 在 suhutdown 时导致崩溃。地址是 0x0000000d,看来 std::string(及其 vtable)被销毁得太早了。

  3. #pragma init_seg(".CRT$XCU-0NN") 在启动和关闭期间按预期工作。如果我解析的内容正确,那么组名 XCU 中的 U 表示用户定义的对象。这意味着我们的对象是在 #pragma init_seg(lib)#pragma init_seg(user) 之间的某处创建的。

下面是如何从源文件 a.cppb.cppc.cpp.

初始化对象 C,然后是对象 B,然后是对象 A

源文件a.cpp:

class A
{
    ...
};

#pragma warning(disable: 4075)
#pragma init_seg(".CRT$XCU-030")
A a;    // created 3rd
#pragma warning(default: 4075)

源文件b.cpp:

class B
{
    ...
};

#pragma warning(disable: 4075)
#pragma init_seg(".CRT$XCU-020")
const B b;    // created 2nd
#pragma warning(default: 4075)

源文件c.cpp:

#pragma warning(disable: 4075)
#pragma init_seg(".CRT$XCU-010")
const std::string c;    // created 1st
const std::string d;    // created 1st
#pragma warning(default: 4075)

我们的用例是创建三个只读对象并避免 C++ 的问题 static initialization order fiasco and Microsoft's thread local storage

该技术避免了在 C++03 中缺少 C++ 动态初始化程序。它还回避了微软无法提供 C++11 的 Dynamic Initialization and Destruction with Concurrency(或者更准确地说,微软未能提供核心语言功能 10 年)。

这里是对 MSDN 上 Thread Local Storage (TLS) 问题的引用:

On Windows operating systems before Windows Vista, __declspec( thread ) has some limitations. If a DLL declares any data or object as __declspec( thread ), it can cause a protection fault if dynamically loaded. After the DLL is loaded with LoadLibrary, it causes system failure whenever the code references the __declspec( thread ) data. Because the global variable space for a thread is allocated at run time, the size of this space is based on a calculation of the requirements of the application plus the requirements of all the DLLs that are statically linked. When you use LoadLibrary, you cannot extend this space to allow for the thread local variables declared with __declspec( thread ). Use the TLS APIs, such as TlsAlloc, in your DLL to allocate TLS if the DLL might be loaded with LoadLibrary.

还值得一提的是,而非似乎对节或组名称中的字符数有限制。存档的 KB 104248 使用 26 个字符的名称 "user_defined_segment_name"