c++ - 如何安排一些代码在所有 '_atexit()' 功能完成后运行

标签 c++ allocation atexit

我正在编写一个内存跟踪系统,我实际遇到的唯一问题是当应用程序退出时,任何未在其构造函数中分配但在其解构函数中释放的静态/全局类正在释放在我的内存跟踪工具将分配的数据报告为泄漏之后。

据我所知,正确解决此问题的唯一方法是强制将内存跟踪器的 _atexit 回调放置在堆栈的头部(以便最后调用它)或让它执行在展开整个 _atexit 堆栈之后。是否真的有可能实现这些解决方案中的任何一个,或者是否有其他我忽略的解决方案。

编辑: 我正在为 Windows XP 工作/开发并使用 VS2005 进行编译。

最佳答案

我终于想出了如何在 Windows/Visual Studio 下执行此操作。再次查看 crt 启动函数(特别是它调用全局初始化器的地方),我注意到它只是运行包含在某些段之间的“函数指针”。因此,只要对链接器的工作原理有一点了解,我就想到了这个:

#include <iostream>
using std::cout;
using std::endl;

// Typedef for the function pointer
typedef void (*_PVFV)(void);

// Our various functions/classes that are going to log the application startup/exit
struct TestClass
{
    int m_instanceID;

    TestClass(int instanceID) : m_instanceID(instanceID) { cout << "  Creating TestClass: " << m_instanceID << endl; }
    ~TestClass() {cout << "  Destroying TestClass: " << m_instanceID << endl; }
};
static int InitInt(const char *ptr) { cout << "  Initializing Variable: " << ptr << endl; return 42; }
static void LastOnExitFunc() { puts("Called " __FUNCTION__ "();"); }
static void CInit() { puts("Called " __FUNCTION__ "();"); atexit(&LastOnExitFunc); }
static void CppInit() { puts("Called " __FUNCTION__ "();"); }

// our variables to be intialized
extern "C" { static int testCVar1 = InitInt("testCVar1"); }
static TestClass testClassInstance1(1);
static int testCppVar1 = InitInt("testCppVar1");

// Define where our segment names
#define SEGMENT_C_INIT      ".CRT$XIM"
#define SEGMENT_CPP_INIT    ".CRT$XCM"

// Build our various function tables and insert them into the correct segments.
#pragma data_seg(SEGMENT_C_INIT)
#pragma data_seg(SEGMENT_CPP_INIT)
#pragma data_seg() // Switch back to the default segment

// Call create our call function pointer arrays and place them in the segments created above
#define SEG_ALLOCATE(SEGMENT)   __declspec(allocate(SEGMENT))
SEG_ALLOCATE(SEGMENT_C_INIT) _PVFV c_init_funcs[] = { &CInit };
SEG_ALLOCATE(SEGMENT_CPP_INIT) _PVFV cpp_init_funcs[] = { &CppInit };


// Some more variables just to show that declaration order isn't affecting anything
extern "C" { static int testCVar2 = InitInt("testCVar2"); }
static TestClass testClassInstance2(2);
static int testCppVar2 = InitInt("testCppVar2");


// Main function which prints itself just so we can see where the app actually enters
void main()
{
    cout << "    Entered Main()!" << endl;
}

哪些输出:

Called CInit();
Called CppInit();
  Initializing Variable: testCVar1
  Creating TestClass: 1
  Initializing Variable: testCppVar1
  Initializing Variable: testCVar2
  Creating TestClass: 2
  Initializing Variable: testCppVar2
    Entered Main()!
  Destroying TestClass: 2
  Destroying TestClass: 1
Called LastOnExitFunc();

这是由于 MS 编写其运行时库的方式。基本上,他们在数据段中设置了以下变量:

(虽然此信息受版权保护,但我认为这是合理使用,因为它不会贬低原件,仅供引用)

extern _CRTALLOC(".CRT$XIA") _PIFV __xi_a[];
extern _CRTALLOC(".CRT$XIZ") _PIFV __xi_z[];    /* C initializers */
extern _CRTALLOC(".CRT$XCA") _PVFV __xc_a[];
extern _CRTALLOC(".CRT$XCZ") _PVFV __xc_z[];    /* C++ initializers */
extern _CRTALLOC(".CRT$XPA") _PVFV __xp_a[];
extern _CRTALLOC(".CRT$XPZ") _PVFV __xp_z[];    /* C pre-terminators */
extern _CRTALLOC(".CRT$XTA") _PVFV __xt_a[];
extern _CRTALLOC(".CRT$XTZ") _PVFV __xt_z[];    /* C terminators */

初始化时,程序简单地从“__xN_a”迭代到“__xN_z”(其中 N 是 {i,c,p,t})并调用它找到的任何非空指针。如果我们只是在段“.CRT$XnA”和“.CRT$XnZ”之间插入我们自己的段(其中,n 再次为 {I,C,P,T}),它将与其他所有内容一起被调用通常会被调用。

链接器只是按字母顺序连接段。这使得选择何时调用我们的函数变得非常简单。如果您查看 defsects.inc(在 $(VS_DIR)\VC\crt\src\ 下找到),您可以看到 MS 已经放置了所有“用户”以“U”结尾的段中的初始化函数(即在代码中初始化全局变量的函数)。这意味着我们只需要将我们的初始化器放在 'U' 之前的段中,它们将在任何其他初始化器之前被调用。

你必须非常小心,不要使用任何在你选择放置函数指针之后才初始化的功能(坦率地说,我建议你就这样使用 .CRT$XCT只有你的代码还没有被初始化。我不确定如果你用标准的“C”代码链接会发生什么,你可能必须把它放在 .CRT$XIT在这种情况下阻止)。

我发现的一件事是,如果链接到运行时库的 DLL 版本,“预终止符”和“终止符”实际上并未存储在可执行文件中。因此,您不能真正将它们用作通用解决方案。相反,我让它作为最后一个“用户”函数运行我的特定函数的方式是简单地在“C 初始值设定项”中调用 atexit(),这样,就没有其他函数可以添加到堆栈(将按照添加函数的相反顺序调用,这也是调用全局/静态解构函数的方式)。

只是最后一个(明显的)说明,这是在考虑 Microsoft 的运行时库的情况下编写的。它可能在其他平台/编译器上工作类似(希望您能够将段名称更改为它们使用的任何名称,如果它们使用相同的方案)但不要指望它。

关于c++ - 如何安排一些代码在所有 '_atexit()' 功能完成后运行,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1753042/

相关文章:

java - 如何将此 HTML 文本转换为 SQLite 数据库?

c++ - CreateThread参数失败

c++ - 分配运算符重载

c++ - 内存分配/解除分配?

python - sys.exitfunc 在 python 中不工作

c++ - 如何在基于 MFC 对话框的应用程序中为复选框捕获 MouseMove 事件?

c++ - 如何使用 XS 连接 C++ 和 Perl?

c++ - 原始数据类型在循环内分配了多少次?

c - 检索对通过 atexit() 注册的函数的引用

c - 如何使用 atexit() 函数来清理函数调用?