Reputation: 58451
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
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
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