_CrtSetAllocHook-未处理的异常

时间:2019-12-08 15:26:14

标签: c++ c visual-studio visual-c++

我使用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

怎么了?如何解决?

2 个答案:

答案 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;
}