Ali
Ali

Reputation: 58451

Why does GetLastError() return 0 or 2 depending on how it is called?

I am using mingw g++ 4.6.1 with -O0, WinXP SP2.

Minimal working example is here.

g++ is configured with --disable-sjlj-exceptions --with-dwarf2.

GetLastError() returns 0 or 2 depeding on how the exception is thrown:

throw runtime_error(error_message());

bogus "error code: 0" is printed, and

const string msg = error_message();

throw runtime_error(msg);

prints "error code: 2" as expected.

First, I thought GetLastError() is invoked twice but debugging shows it is invoked exactly once, as expected.

What is going on?

Upvotes: 5

Views: 3656

Answers (2)

Michael Burr
Michael Burr

Reputation: 340208

If you look at the assembly code generated, it become clear what's happening. The following C++ code:

hDevice = CreateFileA(path, // drive to open
    // etc...
    );

if (hDevice == INVALID_HANDLE_VALUE) // cannot open the drive  
{                                                              
    throw runtime_error(error_message());
}

Generates a stretch of assembly code (at least using default optimization):

    call    _CreateFileA@28  #
LEHE4:
    sub esp, 28  #,
    mov DWORD PTR [ebp-12], eax  # hDevice, D.51673
    cmp DWORD PTR [ebp-12], -1   # hDevice,
    jne L5   #,
    mov DWORD PTR [esp], 8   #,

    call    ___cxa_allocate_exception    # // <--- this call is made between the 
                                             # //    CreateFile() call and the 
                                             # //    error_message() call

    mov ebx, eax     # D.50764,
    lea eax, [ebp-16]    # tmp66,
    mov DWORD PTR [esp], eax     #, tmp66
LEHB5:
    call    __Z13error_messagev  #

You see a call made to ___cxa_allocate_exception to allocate some memory block for the exception being thrown. That function call is changing the GetLastError() state.

When the C++ code looks like:

hDevice = CreateFileA(path, // drive to open
    // etc...
    );

if (hDevice == INVALID_HANDLE_VALUE) // cannot open the drive  
{                                                              
    const string msg = error_message();

    throw runtime_error(msg);
}

Then you get the following generated assembly:

    call    _CreateFileA@28  #
    sub esp, 28  #,
    mov DWORD PTR [ebp-12], eax  # hDevice, D.51674
    cmp DWORD PTR [ebp-12], -1   # hDevice,
    jne L5   #,
    lea eax, [ebp-16]    # tmp66,
    mov DWORD PTR [esp], eax     #, tmp66
    call    __Z13error_messagev  #
LEHE4:
    sub esp, 4   #,
    mov DWORD PTR [esp], 8   #,

    call    ___cxa_allocate_exception  # // <--- now this happens *after*
                                         //     error_message() has been called

which does not call an external function between the failed CreateFile() call and the call to error_message().

This kind of problem is one of the main problems with error handling using some global state like GetLastError() or errno.

Upvotes: 8

Greg Hewgill
Greg Hewgill

Reputation: 993085

It's possible that the code that sets up a throw calls a Win32 API function inside itself somewhere, that resets the Last-Error value to 0. This may be happening before your call to error_message().

Calling GetLastError() does not automatically reset the Last-Error value to 0, so it is safe to call twice.

Whether your compiler/runtime generates code that calls a Win32 API function will be up to your specific runtime. In order to be safe and not depend on this, use the two-statement version:

const string msg = error_message();
throw runtime_error(msg);

Better yet, for future readers of your code it would be useful to call GetLastError() outside error_message():

const string msg = error_message(GetLastError());
throw runtime_error(msg);

This way, readers will see the GetLastError() call immediately after the corresponding Win32 API call, where it belongs.

Upvotes: 10

Related Questions