简体   繁体   中英

What is the REALLY correct way (time) to use GetLastError()?

Following an extended argument here on Stack Overflow (which has now been cleaned up by the Powers-that-Be), the question has arisen as to when one should really call the GetLastError function.

Note: This is not a question about style, just about: (a) what the standard guarantees (or not) in the case of the 'non-purist' code; (b) in terms of safe winapi programming, what is the 'best practice'.

Here's an example (adapted from the original question there posted):

#include <windows.h>
#include <stdio.h>
#include <iostream>

//#define PURIST 1

using namespace std;
int main()
{
    HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
    SMALL_RECT wSize = { 0,0,60,20 }; // Works on my screen!
    if (hConsole == nullptr) {
        cout << "Console Handle is Null" << endl;
        return 1;
    }
    else {
        char message[256];
        if (!SetConsoleWindowInfo(hConsole, TRUE, &wSize)) {
            #ifdef PURIST
            // 'Purist' code ...
            DWORD eCode = GetLastError();
            sprintf(message, "SetConsoleWindowInfo failed; code = %d!", eCode);
            #else
            // More normal code ...
            sprintf(message, "SetConsoleWindowInfo failed; code = %d!", GetLastError());
            #endif
        }
        else {
            strcpy(message, "SetConsoleWindowInfo call succeeded!");
        }
        cout << message << endl;
    }
    getchar(); // Just to stop console closing!
    return 0;
}

Obviously, the 'purist' approach will always work properly! However, does the c++ language standard guarantee that the 'normal' approach will also work? (That is, can one be sure that GetLastError() as an argument to sprintf will be the first code to execute after testing the return value of SetConsoleWindowInfo() ?)

PS: Please don't judge me too harshly on the quality of the code! As I said, it is an adaptation of the original question.

EDIT: A more typical situation (that I use a lot in my Windows apps) looks like this:

if (<WinApi call failed>) {
    TCHAR eText[256];
    FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, nullptr, GetLastError(), 0, eText, 255, nullptr);
    // Do something with eText, etc. ...
    return <error code>;
}

But again, GetLastError() is the only non-const argument.

I will separate this question into two independent questions:

  1. Does "normal code" example work as expected?
    Yes, this exact code will work as expected. Other function arguments are built-in types and even though evaluation of order function arguments is not defined none of them can interfere with GetLastError invocation.
  2. Is "normal code" example a correct way to invoke GetLastError ?
    It is not, even though running this code yields expected results. And the reason for this is that use of GetLastError as function argument introduces implicit constraint on other function arguments that they must have no last-error changing side effects. This makes code more error prone and more difficult to maintain.

So the Rule of thumb would be to store last error value in some variable prior to calling other functions or creating / destroying objects.

The non-purist way is fine here

When calling a function (whether or not the function is inline, and whether or not explicit function call syntax is used), every value computation and side effect associated with any argument expression, or with the postfix expression designating the called function, is sequenced before execution of every expression or statement in the body of the called function.

Rule 3 , I think this corresponds to §8.2.2.5 in the draft version of the C++17 standard.

And in this case there aren't any other arguments with side-effects.

But note that it's fragile, and if someone does something naughty like

#define sprintf (log_call_to_sprintf(), sprintf)

you're out of luck.

let we have next code snippet

if (<WinApi call failed>) {
    SomeApiCall(arg<1>, .., GetLastError(), .., arg<n>);
}

and assume next conditions:

  1. SomeApiCall not a macro expanded to something else - this is exactly some function call
  2. all arguments to function ( arg<1> ... arg<n> ), except GetLastError() , used only internal c/c++ language expressions , which calculated without any external functions calls (including language support libraries)
  3. nothing exist in arguments, what can lead to hardware exception, like divide by zero, reference memory by pointer, etc

  4. this not question about coding style

for example this call

SomeApiCall( "some text", GetLastError(), 8);

conform to 2 and 3

in this case we can say that GetLastError() will be called before thread's last-error code value can be changed, after <WinApi call failed> . because:

  • thread's last-error code can be changed only as result of some windows api calls
  • all arguments to function ( SomeApiCall ) calculated before execution of every expression or statement in the body of the called function
  • so between <WinApi call failed > and SomeApiCall exist only arg<1> , .., GetLastError() , .., arg<n>
  • due 2 and 3 - no any direct or indirect (in exception handler) windows api call, because c/c++ language don't know about windows api and can not by self call it (without some external for language calls, including language support libraries)
  • so between <WinApi call failed > and GetLastError() call - no any windows api calls
  • as result value of last-error code will be not changed between <WinApi call failed > and GetLastError() call

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM