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?
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.
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
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With