Gra*_*ers 3 c++ allocation atexit
我正在编写一个内存跟踪系统,我实际遇到的唯一问题是,当应用程序退出时,任何未在其构造函数中分配但在解构器中解除分配的静态/全局类都会在我的内存之后释放跟踪内容已将分配的数据报告为泄漏.
我可以告诉我,正确解决这个问题的唯一方法是强制将内存跟踪器的_atexit回调放置在堆栈的头部(以便最后调用它)或者让它在整个执行后执行_atexit堆栈已解除.实际上是否可以实现这些解决方案中的任何一种,或者是否存在我忽略的另一种解决方案.
编辑:我正在开发/开发Windows XP并使用VS2005进行编译.
我终于想出了如何在Windows/Visual Studio下执行此操作.再次查看crt启动函数(特别是它调用globals的初始值设定项),我注意到它只是运行某些段之间包含的"函数指针".因此,只需了解链接器的工作原理,我想出了这个:
#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;
}
Run Code Online (Sandbox Code Playgroud)
哪个输出:
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();
Run Code Online (Sandbox Code Playgroud)
这是因为MS编写运行时库的方式.基本上,他们在数据段中设置了以下变量:
(虽然这个信息是版权所有,但我相信这是合理使用,因为它不会使原件贬值而且IS仅供参考)
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 */
Run Code Online (Sandbox Code Playgroud)
在初始化时,程序简单地从'__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版本,"预终结符"和"终结符"实际上并不存储在可执行文件中.因此,您无法真正将它们用作通用解决方案.相反,我使用它的方式运行我的特定函数作为最后一个"用户"函数是简单地atexit()在'C初始化器'中调用,这样,没有其他函数可以添加到堆栈(将在反向调用命令添加了哪些函数,以及全局/静态解构器的调用方式.
只是最后一个(显而易见的)注释,这是用Microsoft的运行时库编写的.它可能在其他平台/编译器上工作类似(希望你能够通过将段名更改为他们使用的任何内容,如果他们使用相同的方案),但不要指望它.