2011-12-29 7 views
5

Sto usando mingw g ++ 4.6.1 con -O0, WinXP SP2.Perché GetLastError() restituisce 0 o 2 a seconda di come viene chiamato?

Minimal working example is here.

g ++ è configurato con --disable-sjlj-eccezioni --with-DWARF2.

GetLastError() restituisce 0 o 2 depeding di come viene generata l'eccezione:

throw runtime_error(error_message()); 

fasullo "il codice di errore: 0" viene stampato, e

const string msg = error_message(); 

throw runtime_error(msg); 

stampe "codice di errore: 2" come previsto.

In primo luogo, ho pensato che GetLastError() sia invocato due volte ma il debug mostra che è stato invocato esattamente una volta, come previsto.

Cosa sta succedendo?

+0

Cosa succede se modifichi il tuo codice per chiamare 'error_message (GetLastError())'? –

+0

Ti rendi conto, ovviamente, che "GetLastError()", C++ tenta/cattura le eccezioni e Win32 "Structured Exception Handling" (SEH) sono tutte e tre le cose (correlate, ma diverse), vero? Generalmente usi l'uno o l'altro, ma tu * non dovresti * generalmente usarli * insieme *, in combinazione l'uno con l'altro. – paulsm4

+0

@GregHewgill Voglio nascondere GetLastError(), e vorrei invece usare error_message(). – Ali

risposta

9

E 'possibile che il codice che imposta una throw chiama una funzione API Win32 dentro di sé da qualche parte, che azzera il valore Last-errore a 0. Questo può accadere prima la chiamata a error_message().

Calling GetLastError() fa non reimpostare automaticamente il valore Last-errore a 0, quindi è sicuro chiamare due volte.

Se il tuo compilatore/runtime genera codice che richiama una funzione API Win32 sarà al tuo specifico runtime. Al fine di essere sicuro e non dipendere da questo, utilizzare la versione a due dichiarazioni:

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

Meglio ancora, per i futuri lettori del vostro codice sarebbe utile per chiamare GetLastError() fuori error_message():

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

In questo modo, i lettori vedranno la chiamata GetLastError() immediatamente dopo la corrispondente chiamata API Win32, a cui appartiene.

8

Se si guarda il codice assembly generato, diventa chiaro cosa sta succedendo. Il seguente codice C++:

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

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

genera un tratto di codice assembly (almeno utilizzando ottimizzazione predefinito):

call [email protected] # 
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 # 

Si vede una chiamata fatta a ___cxa_allocate_exception per allocare qualche blocco di memoria per l'all'eccezione generata . Quella chiamata di funzione sta cambiando lo stato GetLastError().

Quando il codice C++ si presenta come:

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); 
} 

Quindi si ottiene la seguente assembly generato:

call [email protected] # 
    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 

che non richiede una funzione esterna tra il fallito CreateFile() chiamata e la chiamata a error_message() .

Questo tipo di problema è uno dei problemi principali con la gestione degli errori utilizzando uno stato globale come GetLastError() o errno.

+0

+1 Grazie per i vostri sforzi. Può suggerire una soluzione pulita per risolvere questo problema? O la risposta di Greg Hewgill è la migliore che possiamo fare? – Ali

+0

La risposta di Greg Hewgill è il modo corretto per avvicinarsi a questo (questa risposta stava davvero cercando di rendere concreto il punto di Greg). Dato che non hai realmente il controllo su cosa fa un compilatore per implementare 'throw' (o qualsiasi altra istruzione per quella materia), non puoi avere una dichiarazione' throw' nel mix di ottenere GetLastError() ' stato. Devi 'GetLastError()' rigorosamente prima del 'lancio'. –

+0

+1 Grazie per l'analisi. –

Problemi correlati