2011-12-29 30 views
5

Tôi đang sử dụng mingw g ++ 4.6.1 với -O0, WinXP SP2.Tại sao GetLastError() trả lại 0 hoặc 2 tùy thuộc vào cách nó được gọi?

Minimal working example is here.

g ++ được cấu hình với --disable-sjlj-ngoại lệ --with-dwarf2.

GetLastError() trả về 0 hoặc 2 depeding về cách ngoại lệ được ném:

throw runtime_error(error_message()); 

giả "mã lỗi: 0" được in, và

const string msg = error_message(); 

throw runtime_error(msg); 

in "mã lỗi: 2" như kỳ vọng.

Trước tiên, tôi nghĩ GetLastError() được gọi hai lần nhưng gỡ lỗi cho thấy nó được gọi chính xác một lần, như mong đợi.

Điều gì đang xảy ra?

+0

Điều gì sẽ xảy ra nếu bạn sửa đổi mã của mình để gọi 'error_message (GetLastError())'? –

+0

Bạn nhận ra, tất nhiên, rằng "GetLastError()", C++ try/catch exception, và Win32 "Structured Exception Handling" (SEH) là tất cả ba thứ khác nhau (liên quan, nhưng khác nhau), phải không? Bạn thường sử dụng một hoặc khác, nhưng bạn * không nên * thường sử dụng chúng * cùng nhau *, kết hợp với nhau. – paulsm4

+0

@GregHewgill Tôi muốn ẩn GetLastError() và tôi muốn sử dụng error_message() thay thế. – Ali

Trả lời

9

Có thể là mã mà thiết lập một throw gọi một hàm Win32 API bên trong bản thân ở đâu đó, mà reset giá trị cuối-Lỗi để 0. Điều này có thể xảy ra trước khi cuộc gọi của bạn để error_message().

Gọi GetLastError() không không tự động đặt lại giá trị Last-Error thành 0, vì vậy an toàn để gọi hai lần.

Cho dù trình biên dịch/thời gian chạy của bạn tạo ra mã gọi hàm Win32 API sẽ tùy thuộc vào thời gian chạy cụ thể của bạn. Để được an toàn và không phụ thuộc vào điều này, sử dụng phiên bản hai tuyên bố:

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

Hơn thế nữa, đối với độc giả tương lai của mã của bạn nó sẽ hữu ích để gọi GetLastError() ngoài error_message():

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

Bằng cách này, người đọc sẽ thấy cuộc gọi GetLastError() ngay sau cuộc gọi API Win32 tương ứng, nơi nó thuộc về.

8

Nếu bạn nhìn vào mã lắp ráp được tạo, nó sẽ trở nên rõ ràng những gì đang xảy ra. CáC++ C code sau:

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

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

Tạo một đoạn mã lắp ráp (ít nhất là sử dụng tối ưu hóa mặc định):

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 # 

Bạn thấy một cuộc gọi thực hiện để ___cxa_allocate_exception phân bổ một số khối bộ nhớ cho các ngoại lệ được ném . Cuộc gọi chức năng đó đang thay đổi trạng thái GetLastError().

Khi C++ trông giống như:

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

Sau đó, bạn sẽ có được lắp ráp tạo sau:

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 

mà không gọi một chức năng bên ngoài giữa thất bại CreateFile() cuộc gọi và cuộc gọi đến error_message() .

Loại sự cố này là một trong những vấn đề chính với xử lý lỗi khi sử dụng một số trạng thái toàn cầu như GetLastError() hoặc errno.

+0

+1 Cảm ơn những nỗ lực của bạn. Có thể đề xuất một giải pháp sạch để giải quyết vấn đề này? Hay là câu trả lời của Greg Hewgill tốt nhất chúng ta có thể làm? – Ali

+0

Câu trả lời của Greg Hewgill là cách chính xác để tiếp cận điều này (câu trả lời này thực sự chỉ là cố gắng làm cho điểm của Greg). Vì bạn không thực sự kiểm soát được trình biên dịch thực hiện 'throw' (hoặc bất kỳ câu lệnh nào khác cho vấn đề đó), bạn không thể có câu lệnh 'throw' được ném vào hỗn hợp nhận' GetLastError() ' trạng thái. Bạn phải 'GetLastError()' nghiêm ngặt trước khi 'ném'. –

+0

+1 Cảm ơn bạn đã phân tích. –

Các vấn đề liên quan