在 HeapAlloc() 失败后调用 GetLastError() 是否可靠?

Is it reliable to call GetLastError() after a failed HeapAlloc()?

提问人:vengy 提问时间:11/16/2023 最后编辑:vengy 更新时间:11/18/2023 访问量:134

问:

我最近在看一些代码,这些代码在失败后确实调用了。但是,根据 HeapAlloc 函数 MSDN 文档:GetLastError()HeapAlloc()

如果函数失败,则不会调用 SetLastError。应用程序无法调用 GetLastError 以获取扩展的错误信息。

互联网上充斥着在失败后调用的代码片段,我假设大多数编码人员(包括我自己)都被编程为在 API 失败时自然调用。GetLastError()HeapAlloc()GetLastError()

为了测试,我创建了这个示例来分配最大内存量:

#include <windows.h>
#include <stdio.h>
#include <stdint.h>

int main() {
    HANDLE hHeap = GetProcessHeap();
    if (hHeap == NULL) {
        printf("Failed to get the process heap.\n");
        return 1;
    }

    SetLastError(0);  // Clear the last error

    // Attempt to allocate a very large amount of memory
    SIZE_T largeSize = SIZE_MAX;
    LPVOID pMemory = HeapAlloc(hHeap, 0, largeSize);

    if (pMemory == NULL) {
        DWORD dwError = GetLastError();
        printf("HeapAlloc failed: ");

        switch (dwError) {
        case ERROR_NOT_ENOUGH_MEMORY:
            printf("Not enough memory available.\n");
            break;
        case ERROR_OUTOFMEMORY:
            printf("Insufficient memory to satisfy the request.\n");
            break;
        default:
            printf("Error code: %lu\n", dwError);
        }
    }
    else {
        printf("Memory allocation successful.\n");
        HeapFree(hHeap, 0, pMemory);
    }

    return 0;
}

输出显示:

HeapAlloc 失败:可用内存不足。

问题

为什么看起来有效?难道只是运气好,所以大家都应该避免打电话,因为依靠详细的错误信息可能不可靠?GetLastError()GetLastError()

更新

笔记的官方文档:HeapAlloc()

如果函数失败,则不会调用 SetLastError。应用程序无法调用 GetLastError 以获取扩展的错误信息。

但是,从下面的注释中可以看出,TEB 中的内部实现更新了,但未为外部开发人员记录。HeapAlloc()LastErrorValue

下面是在 TEB 中使用 8 进行更新的示例。HeapAllocLastErrorValueERROR_NOT_ENOUGH_MEMORY

#include <windows.h>
#include <stdio.h>
#include <stdint.h>
#include <intrin.h>

typedef struct _MY_TEB {
    // The TEB is highly stable across Windows versions.
    uint8_t Padding[0x68];
    ULONG LastErrorValue; 
    // ...
} MY_TEB;

int main() {
    SetLastError(0);
    printf("LastErrorValue (TEB): %lu\n", ((MY_TEB*)__readgsqword(0x30))->LastErrorValue);
    printf("GetLastError (API):   %lu\n", GetLastError());

    HeapAlloc(GetProcessHeap(), 0, SIZE_MAX);
    printf("LastErrorValue (TEB): %lu\n", ((MY_TEB*)__readgsqword(0x30))->LastErrorValue);
    printf("GetLastError (API):   %lu\n", GetLastError());

    return 0;
}

哪个输出

LastErrorValue (TEB): 0
GetLastError (API):   0
LastErrorValue (TEB): 8
GetLastError (API):   8

总而言之,我会坚持使用官方文档,不要调用.谢谢。GetLastError()

Windows WinAPI

评论

0赞 Jeff Holt 11/16/2023
有两种可能性:(a)文档的那部分曾经是真的,但现在不再是真的,或者(b)“不打电话”应该改为“可能不会打电话”。
1赞 RbMm 11/17/2023
SetLastError当然没有调用(因为 HeapAlloc == ntdll.dll 中的 RtlAllocateHeap),而是直接设置在 中,如果它失败了。RtlAllocateHeapLastErrorValueTEB
0赞 vengy 11/17/2023
@RbMm 有趣的是,GetLastError() 是否直接引用 TEB 字段?如果是这样,在 HeapAlloc() 失败后调用 GetLastError() 是否有效?LastErrorValue
0赞 RbMm 11/17/2023
当然,GetLastError() 直接引用 TEB 字段 LastErrorValue。但可能不是在所有 Windows 版本中都设置了 . 是 shell 结束。如果失败 - 它将硬编码的最后一个错误设置为RtlAllocateHeapLastErrorValueLocalAllocRtlAllocateHeapERROR_NOT_ENOUGH_MEMORY
0赞 vengy 11/17/2023
可能的错误包括 ERROR_SUCCESS、ERROR_INVALID_PARAMETER、ERROR_NOT_ENOUGH_MEMORY、ERROR_INVALID_BLOCK、ERROR_HEAP_CORRUPTED ERROR_RESOURCE_NOT_FOUND。那么,调用 GetLastError() 是否可靠,或者 msdn 文档是否正确,永远不应该调用它?我正在使用 win11 pro。

答:

2赞 IInspectable 11/17/2023 #1

要测试,[...]

您无法“测试”合同是否有效。

合同是。

前提条件和后置条件的组合称为合同实施有权放宽其前提条件并加强其后置条件。发生这种情况时,它被称为实现细节

实现细节不是合同的一部分。

如果 HeapAlloc 的文档指出

应用程序无法调用扩展的错误信息。GetLastError

那么这就是与其后置条件有关的合同。返回时不要打电话。GetLastErrorHeapAllocNULL