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

Issue

I was looking at some code recently that did call GetLastError() after HeapAlloc() had failed. But, according to the HeapAlloc function MSDN docs:

If the function fails, it does not call SetLastError. An application cannot call GetLastError for extended error information.

The Internet is littered with code snippets that are calling GetLastError() after a failed HeapAlloc(), and I assume most coders (including myself) are programmed to naturally call GetLastError() when an API fails.

To test, I created this example to allocate the max amount of memory:

#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;
}

The output shows:

HeapAlloc failed: Not enough memory available.

Question

Why does GetLastError() appear to work? Is it just luck, so everyone should avoid calling it because relying on GetLastError() for detailed error information may not be reliable?

Update

The official documentation for HeapAlloc() notes:

If the function fails, it does not call SetLastError. An application cannot call GetLastError for extended error information.

However, from the comments below, the internal implementation of HeapAlloc() updates the LastErrorValue in the TEB, but is not documented for external developers.

Here’s an example of HeapAlloc updating LastErrorValue in the TEB with ERROR_NOT_ENOUGH_MEMORY 8.

#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;
}

which outputs

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

To sum up, I’ll stick with the official documentation to NOT call GetLastError(). Thanks.

Solution

To test, […]

You cannot "test" whether a contract is in effect.

Contracts are.

The combination of preconditions and postconditions is called a contract. An implementation is entitled to loosen its preconditions and strengthen its postconditions. When that happens, it is called an implementation detail.

Implementation details aren’t part of the contract.

If the documentation for HeapAlloc states that

An application cannot call GetLastError for extended error information.

then that is the contract pertaining to its postcondition. Don’t call GetLastError when HeapAlloc returns NULL.

Answered By – IInspectable

This Answer collected from stackoverflow, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply

(*) Required, Your email will not be published