Stefan Stefan - 3 months ago 45
C++ Question

Why is std::string constructor resetting GetLastError

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 function is

void throw_system_error(const std::string& msg_prefix, DWORD messageid)


I've noticed something strange. This code does not work properly:

handle = ...;
if (handle == NULL) {
throw_system_error("something bad happened", GetLastError());
}


The error code that is passed to
throw_system_error
is always zero.

Where as this works just fine:

handle = ...;
if (handle == NULL) {
DWORD error = GetLastError();
throw_system_error("something bad happened", error);
}


Some more investigation showed that this version has the same problem:

handle = ...;
if (handle == NULL) {
std::string msg("something bad happened");
DWORD error = GetLastError();
throw_system_error(msg, error);
}


It looks for all the world as if the constructor of
std::string
is resetting the error code.

My guess would be that
std::string
is allocating memory internally which causes some system call that then sets the last error back to zero.

Anyone knows what is actually going on here?

Visual C++ 2015, 64bit.

Answer

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 answer in Stack Overflow : http://programmers.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
Comments