我使用Windows 10 x64和Visual Studio2019。这是一个简单的程序(x86-Debug),应打印内存分配大小:
#include <iostream>
int MyAllocHook(int allocType, void* userData, std::size_t size, int blockType, long requestNumber,
const unsigned char* filename, int lineNumber)
{
std::cout << "Alloc: " << size << std::endl;
return 1;
}
int main()
{
_CrtSetAllocHook(MyAllocHook);
void* ptr = malloc(128);
if (ptr)
{
free(ptr);
}
system("pause");
return 0;
}
运行程序时,出现异常未处理错误:
Unhandled exception at 0x7A6A2B19 (ucrtbased.dll) in testcpp.exe: 0xC00000FD: Stack overflow (parameters: 0x00000000, 0x00EB2000). occurred
怎么了?如何解决?
答案 0 :(得分:3)
“堆栈溢出”(哈哈)是我的礼物。
钩子中的I / O和字符串操作导致了其自身的内存分配,因此该钩子一次又一次地被调用,直到溢出堆栈为止。
您必须查看blockType
并明确排除运行时本身的调用,如此处所述:https://docs.microsoft.com/en-us/visualstudio/debugger/allocation-hooks-and-c-run-time-memory-allocations?view=vs-2019
int MyAllocHook(int allocType, void* userData, std::size_t size,
int blockType, long requestNumber,
const unsigned char* filename, int lineNumber)
{
// Do not hook calls from the C Runtime itself
if (blockType == _CRT_BLOCK) return TRUE;
std::cout << "Alloc: " << size << std::endl;
return 1;
}
这样,来自I / O库本身的内存分配调用不会引起递归调用。
EDIT 啊哈,您不是只是被C运行时调用挂断了,它也来自您自己的“常规”字符串和I / O调用,它们是不是从运行时。基本上,您是在处理程序中间接调用malloc()
和/或new
,但这是不允许的。
此测试程序可让您查看分配的疯狂内存:
#include <iostream>
struct allocinfo {
int size;
const char *filename;
int lineno;
int alloctype;
int blocktype;
};
struct allocinfo allocs[256], *allocp = allocs;
volatile bool capturing = true;
static const char *printableAllocType(int t)
{
switch (t)
{
case _HOOK_ALLOC: return "_HOOK_ALLOC";
case _HOOK_REALLOC: return "_HOOK_REALLOC";
case _HOOK_FREE: return "_HOOK_FREE";
default: return "?";
}
}
static const char *printableBlockType(int t)
{
switch (t)
{
case _FREE_BLOCK: return "_FREE_BLOCK";
case _NORMAL_BLOCK: return "_NORMAL_BLOCK";
case _CRT_BLOCK: return "_CRT_BLOCK";
case _IGNORE_BLOCK: return "_IGNORE_BLOCK";
case _CLIENT_BLOCK: return "_CLIENT_BLOCK";
default: return "?";
}
}
int MyAllocHook( int allocType, void* userData, std::size_t size,
int blockType, long requestNumber,
const unsigned char* filename, int lineNumber)
{
if (blockType == _CRT_BLOCK) return 1;
if (capturing)
{
allocp->size = (int)size;
allocp->lineno = lineNumber;
allocp->blocktype = blockType;
allocp->alloctype = allocType;
allocp->filename = (const char *)filename;
allocp++;
}
static bool firstTime = true;
if (firstTime)
{
firstTime = false;
std::cout << "Alloc : " << size << std::endl;
}
return 1;
}
int main()
{
_CrtSetAllocHook(MyAllocHook);
void* ptr = malloc(128);
if (ptr)
{
free(ptr);
}
capturing = false;
for (struct allocinfo *ap = allocs; ap < allocp; ap++)
printf("%-15s %-15s Size %d file %s line %d\n",
printableAllocType(ap->alloctype),
printableBlockType(ap->blocktype),
ap->size, ap->filename, ap->lineno);
return 0;
}
它以不分配任何内存的方式(应该是安全的)记录所有 non C运行时调用,并在最后报告您的请求生成了您期望的明显128字节,再加上处理程序中字符串代码的另外两个16字节分配:
Alloc : 128
_HOOK_ALLOC _NORMAL_BLOCK Size 128 file (null) line 0 <-- EXPECTED
_HOOK_ALLOC _NORMAL_BLOCK Size 16 file (null) line 0 <-- SURPRISE!
_HOOK_ALLOC _NORMAL_BLOCK Size 16 file (null) line 0 <-- SURPRISE!
_HOOK_FREE _NORMAL_BLOCK Size 0 file (null) line 0
_HOOK_FREE _NORMAL_BLOCK Size 0 file (null) line 0
_HOOK_FREE _NORMAL_BLOCK Size 0 file (null) line 0
代码本身被完全砍在一起,但是它演示了正在发生的事情。
注意:如另一个答案所述,此代码 not 不能正确重入。
总结:即使正确地排除了C运行时内存,您也无法在直接或间接分配用户内存的内存挂钩中做任何事情。
有趣的事实:将_CRT_BLOCK
检查移到 之后,您将看到C运行时的繁忙程度。
编辑-我刚刚修改了我的小测试程序,发现对malloc()
和new
的调用都被视为“正常”内存块,而不是运行时,因此建议您根本不能在内存挂钩处理程序中使用它们。
答案 1 :(得分:1)
在调用_CrtSetAllocHook
时,应在回调函数中处理reentrat调用案例。
如果您的MyAllocHook
函数代码也试图分配内存,则可能导致这种情况。
一种简单的方法是(取自StackWalker):
static LONG g_lMallocCalled = 0;
int MyAllocHook(int allocType, void* userData, std::size_t size, int blockType, long requestNumber,
const unsigned char* filename, int lineNumber)
{
// Prevent from reentrat calls
if (InterlockedIncrement(&g_lMallocCalled) > 1) { // I was already called
InterlockedDecrement(&g_lMallocCalled);
return TRUE;
}
std::cout << "Alloc: " << size << std::endl;
InterlockedDecrement(&g_lMallocCalled);
return TRUE;
}