1

init_seg用来控制三个 C++ 类对象的创建。每个对象位于不同的源文件/翻译单元中。调试显示在 CRT 初始化期间正在按预期创建对象。

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

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

看起来liband的使用section-name是互斥的,所以我不清楚如何使用init_seg(lib)和提供部分/组名称来获得按字母顺序排列的权利。

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

#pragma init_seg(lib, "01")

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

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

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

#pragma init_seg(".CRT$XCB")

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

warning C4075: initializers put in unrecognized initialization area

我在 Stack Overflow 上发现了一个关于它的问题,但答案是猜测,它不包括多个翻译单元。我还在 Wayback Machine 上找到了KB104248的存档,但它也没有太大帮助,因为它只显示了compiler,libuser.

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

4

2 回答 2

3

以下是我通过在 XP 和 VS2002/VS2003、Vista 和 VS2005/VS2008、Windows 7 和 VS2008/VS2010、Windows 8 和 VS2010/VS2012/VS2013 以及使用 VS2015 的 Windows 10 上的测试发现的。#pragma_init(<name>)从 VC++ 1.0 天开始就可以使用了。MS 没有发布太多关于它的信息,但我们知道它从VC++1.0(存档 KB104248)VS2017 都有记录

  1. #pragma init_seg(lib)几乎是完美的。但是,目标文件在 VS2008 及更早版本中按字母顺序排列,因此初始化顺序是a-b-c(不需要的)而不是c-b-a(所需的)。在 VS2010 及更高版本上还可以。不明显的是订单的布局c-b-avcproj文件中的完全相同。

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

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

因此,这里是如何初始化对象 C,然后是对象 B,然后是源文件中的对象 A a.cppb.cpp以及c.cpp.

源文件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++ 的静态初始化顺序惨败和微软的线程本地存储的问题。

该技术避免了 C++03 中缺少 C++ 动态初始化程序。它还回避了微软无法提供 C++11 的并发动态初始化和销毁​​(或者更准确地说,微软未能提供核心语言功能 10 年)。

这是对 MSDN上Thread Local Storage (TLS)问题的参考:

在 Windows Vista 之前的 Windows 操作系统上,__declspec(thread) 有一些限制。如果 DLL 将任何数据或对象声明为 __declspec(thread),则在动态加载时可能会导致保护错误。使用 LoadLibrary 加载 DLL 后,只要代码引用 __declspec(thread) 数据,就会导致系统故障。因为线程的全局变量空间是在运行时分配的,所以这个空间的大小是基于对应用程序要求加上所有静态链接的 DLL 要求的计算得出的。使用 LoadLibrary 时,不能扩展此空间以允许使用 __declspec(thread) 声明的线程局部变量。如果 DLL 可能与 LoadLibrary 一起加载,请使用 DLL 中的 TLS API(例如 TlsAlloc)来分配 TLS。

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

于 2017-03-20T16:39:22.357 回答
2

如果您使用自定义部分名称,则需要使用#pragma section( https://msdn.microsoft.com/en-us/library/50bewfwa.aspx ) 来指定该部分的属性

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

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

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

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

来自 tu1.cpp 的数据现在将位于来自 tu2.cpp 的数据之前

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

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

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

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

于 2017-03-20T09:39:15.290 回答