Why is std::string constructor resetting GetLastError

前端 未结 3 1482
走了就别回头了
走了就别回头了 2021-01-13 17:26

I\'m calling Windows APIs from C++ code and I have a helper method to do the FormatMessage stuff and throw an exception for error handling. The signature of the

相关标签:
3条回答
  • 2021-01-13 17:55

    Let's see the GetLastError documentation:

    Most functions that set the thread's last-error code set it when they fail. However, some functions also set the last-error code when they succeed.

    You should call the GetLastError function immediately when a function's return value indicates that such a call will return useful data. That is because some functions call SetLastError with a zero when they succeed, wiping out the error code set by the most recently failed function.

    So there is one function calling SetLastError, very likely one that allocates memory: When you construct a string, new is called to allocate memory.

    Now, let's see new's implementation in vc++. There is a very good answer to that question in Stack Overflow : https://softwareengineering.stackexchange.com/a/293209

    It depends if you are in debug or release mode. In release mode, there is HeapAlloc/HeapFree which are kernel functions,

    while in debug mode (with visual studio) there is a hand written version of free and malloc (to which new/delete are re-directed) with thread locks and more exceptions detection, so that you can detect more easily when you did some mistakes with you heap pointers when running your code in debug mode.

    So in release mode, the function called is HeapAlloc, which does NOT call SetLastError. From the documentation:

    If the function fails, it does not call SetLastError

    So the code should work properly in release mode. However, in the debug implementation, the function FlsGetValue is called, and that function calls SetLastError when succeeded.

    It's very easy to check this,

    #include <iostream>
    #include <Windows.h>
    int main() {
    
        DWORD t = FlsAlloc(nullptr);
        SetLastError(23); //Set error to 23
        DWORD error1 = GetLastError(); //store error
    
        FlsGetValue(t); //If success, it is going to set error to 0
    
        DWORD error2 = GetLastError(); //store second error code
    
        std::cout << error1 << std::endl;
        std::cout << error2 << std::endl;
        system("PAUSE");
        return 0;
    }
    

    It outputs the following:

    23
    0
    

    So FlsGetValue has called SetLastError(). To prove that it is called only on debug we can do the following test:

    #include <iostream>
    #include <Windows.h>
    int main() {
    
        DWORD t = FlsAlloc(nullptr);
        SetLastError(23); //Set error to 23
        DWORD error1 = GetLastError(); //store error
    
        int* test = new int; //allocate int
    
        DWORD error2 = GetLastError(); //store second error code
    
        std::cout << error1 << std::endl; //output errors
        std::cout << error2 << std::endl;
    
        delete test; //free allocated memory
        system("PAUSE");
        return 0;
    }
    

    If you run it in debug mode, it will give you, because it calls FlsGetValue:

    23
    0
    

    However, if you run it in release mode, it produces, because it calls HeapAlloc:

    23
    23
    
    0 讨论(0)
  • 2021-01-13 17:56

    This is normal - the "last error" can be set indirectly through any function call.
    Some functions set it to "no error" on success, so if you want to use it reliably you need to store it immediately before you do anything else.

    If you've ever encountered an "A serious error occurred: The operation completed successfully" dialogue, this is probably the reason.

    0 讨论(0)
  • 2021-01-13 18:00

    Per the documentation for GetLastError

    The Return Value section of the documentation for each function that sets the last-error code notes the conditions under which the function sets the last-error code. Most functions that set the thread's last-error code set it when they fail. However, some functions also set the last-error code when they succeed. If the function is not documented to set the last-error code, the value returned by this function is simply the most recent last-error code to have been set; some functions set the last-error code to 0 on success and others do not.

    At some point during the construction of std::string, SetLastError is called. The standard library on windows uses Win32 calls as part of its implementation.

    Your second method (that works) is the correct way to use GetLastError

    You should call the GetLastError function immediately when a function's return value indicates that such a call will return useful data. That is because some functions call SetLastError with a zero when they succeed, wiping out the error code set by the most recently failed function.

    0 讨论(0)
提交回复
热议问题