首页 > 解决方案 > _CrtSetAllocHook - 未处理的异常

问题描述

我使用 Windows 10 x64 和 Visual Studio 2019。这是一个简单的程序 (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;
}

当我运行程序时,我得到 Exception Unhandled 错误:

Unhandled exception at 0x7A6A2B19 (ucrtbased.dll) in testcpp.exe: 0xC00000FD: Stack overflow (parameters: 0x00000000, 0x00EB2000). occurred

怎么了 ?如何解决?

标签: c++cvisual-studiovisual-c++

解决方案


“堆栈溢出”(哈哈)是我的赠品。

钩子中的 I/O 和字符串操作导致它自己的内存分配,因此钩子被一次又一次地调用,直到它溢出堆栈。

您必须查看blockType并明确排除运行时本身的调用,如此处所述: https ://docs.microsoft.com/en-us/visualstudio/debugger/allocation-hooks-and-c-run-time-内存分配?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 库本身的内存分配调用不会导致递归调用。

编辑啊哈,你不只是被 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;
}

它以一种不分配任何内存(应该是安全的)的方式记录所有C 运行时调用,并在最后报告您的请求生成了您预期的明显 128 字节,以及来自处理程序中的字符串代码:

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

代码本身完全被破解在一起,但它演示了正在发生的事情。

注意:此代码不能正确重入,如另一个答案中所述。

摘要:即使您正确排除了 C 运行时内存,您也无法在直接或间接分配用户内存的内存挂钩中执行任何操作。

有趣的事实:在捕获信息_CRT_BLOCK移动检查,您会看到 C 运行时有多忙。

编辑- 我刚刚修改了我的小测试程序,发现调用malloc()new都被视为“正常”内存块,而不是运行时,所以这表明你根本不能在内存挂钩处理程序中使用它们。


推荐阅读