简体   繁体   中英

C4533 warning: why does goto skip variable initialization?

I'm getting:

warning C4533: initialization of 'b' is skipped by goto FreeDC.

But if the code gets to the label FreeDC in WM_CREATE , ' b ' is not initialized. How its initialization could be skiped, if it is not initialized in that situation. I just don't understand the warning.

#include <windows.h>

class A
{
    int i;

    public:
    A() {};
    A(int i) : i(i) {}
};

LRESULT CALLBACK WndProc(HWND, UINT, UINT, LONG);

HINSTANCE ghInstance;

/************************************************************************************************************************

    WinMain(hInstance, hPrevInstance, pszCmdLine, nCmdShow)

************************************************************************************************************************/

int APIENTRY WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR pszCmdLine, int nCmdShow)
{
    ghInstance = hInstance;

    WNDCLASSEX  wndclassx;

    wndclassx.cbSize        = sizeof(WNDCLASSEX);
    wndclassx.style         = CS_HREDRAW | CS_VREDRAW;
    wndclassx.lpfnWndProc   = WndProc;
    wndclassx.cbClsExtra    = 0;
    wndclassx.cbWndExtra    = 0;
    wndclassx.hInstance     = hInstance;
    wndclassx.hIcon         = NULL;
    wndclassx.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wndclassx.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wndclassx.lpszMenuName  = NULL;
    wndclassx.lpszClassName = L"WndProc";
    wndclassx.hIconSm       = NULL;

    if( !RegisterClassEx(&wndclassx) ) return 0;

    HWND hWnd = CreateWindow(L"WndProc", L"", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
                             CW_USEDEFAULT, NULL, NULL, hInstance, NULL);

    ShowWindow(hWnd, SW_SHOWMAXIMIZED);
    UpdateWindow(hWnd);

    MSG msg;

    while( GetMessage(&msg, NULL, 0, 0) )
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    //  Retorna msg.wParam

    return (int)msg.wParam;
}

/************************************************************************************************************************

    WndProc(hwnd, message, wParam, lParam)

************************************************************************************************************************/

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, UINT wParam, LONG lParam)

{
    static A a;
    static int i;

    switch ( message )
    {
        case WM_CREATE:
        {
            HDC hDC;
            if( !(hDC = GetDC(hwnd)) ) return -1;

            int iLogPixelsY = GetDeviceCaps(hDC, LOGPIXELSY);

            LOGFONT lf;
            memset(&lf, 0, sizeof(LOGFONT));
            lf.lfHeight = -MulDiv(11, iLogPixelsY, 72);
            wcscpy_s(lf.lfFaceName, LF_FACESIZE, L"Cambria Math");

            HFONT hFont;
            if( !(hFont = CreateFontIndirect(&lf)) ) goto FreeDC;

            hFont = (HFONT)SelectObject(hDC, hFont);

            int j = 5;
            i = j;

            A b(2);
            a = b;
            return 0;

            FreeDC: ReleaseDC(hwnd, hDC);
            return -1; 
        }
        break;

        case WM_DESTROY:

        PostQuitMessage(0);
        break;

        default:

        return DefWindowProc(hwnd, message, wParam, lParam);
    }
    return 0;
}

b 's constructor won't called if you use goto , and yet it's still in scope. This is technically an error, although some compilers only give off a warning.

Here's an example:

int main() {
  goto foo;
  int bar = 5;
  foo:
  ++bar; // doesn't work if goto is used - bar isn't initialized
}

It may seem like you are not using b , but its destructor is still being called:

int main() {
  goto foo;
  A b;
  foo:
  b.~A(); // compiler silently adds destructor and other cleanup here
          // won't work if goto is used - b isn't initialized
}

I honestly don't know, but why are you using a goto when an if statement will suffice?

if( (hFont = CreateFontIndirect(&lf)) ) {
    hFont = (HFONT)SelectObject(hDC, hFont);

    int j = 5;
    i = j;

    A b;
    a = b(2);
    return 0;
}
else {
    FreeDC: ReleaseDC(hwnd, hDC);
    return -1; 
}

// break; here is unnecessary.

You must not skip an object's initialization with either goto or switch [*](that holds for user-defined types as well as primitive types like int s). In your case, you are not using the object whose initialization you've skipped, so the best solution would be to make this clear to the compiler by limiting the scope of b .

        if( !(hFont = CreateFontIndirect(&lf)) ) goto FreeDC;

        hFont = (HFONT)SelectObject(hDC, hFont);

        int j = 5;
        i = j;
    {
        A b;
        a = b(2);
        return 0;
    }
        FreeDC: ReleaseDC(hwnd, hDC);

[*] so these would be illegal:

switch(x) {
    case 1:
        int y=1;
    case 2:
        // y not initialized if x==2

and

if (x) goto l;
int y=1;
l: // y not initialized if x!=0

This particularly matters if y is a reference, a constant or a user-defined object with nontrivial constructor.

The standard says it in 6.7/3:

It is possible to transfer into a block, but not in a way that bypasses declarations with initialization. A program that jumps from a point where a local variable with automatic storage duration is not in scope to a point where it is in scope is ill-formed unless the variable has POD type (3.9) and is declared without an initializer (8.5).

You could avoid the problem by introducing a suitable local scope that gets skipped by the goto :

        HFONT hFont;

        if( !(hFont = CreateFontIndirect(&lf)) )
        {
          goto FreeDC;
        }

        hFont = (HFONT)SelectObject(hDC, hFont);

        {               // new scope; skipped entirely by goto
          int j = 5;
          i = j;

          A b;
          a = b(2);
        }

        return 0;

    FreeDC:
        ReleaseDC(hwnd, hDC);
        return -1;

If you think about C++ and scopes and automatic object lifetimes really carefully, you'll come to conclude that goto really wreaks havoc with the entire programming model. That's why there are many (often quietly implied) conditions on where you can go-to and wher not. Generally, jumping into the middle of a scope is problematic if the scope contains new automatic variables. We avoid this by introducing a new, local scope that the goto jump skips entirely.

Consider a smaller, trivial test case:

struct Object {
   Object(int i) : i(i) { }
   int i;
};

int main() {
    Object a(5);
    goto Label;
    Object b(6);
  Label:
    cout << a.i << " " << b.i << endl;
}

At that last line, ai is obviously 5 . But what is the value of bi ? When that object was created, it was supposed to be initialized to 6 , but you explicitly told the program to skip that line. It could be anything.

Now, lets pretend Object is a more useful type:

struct Object {
  Object(int i) : p(new int(i)) { }
  ~Object() { delete p; }
  //insert copy/move constructors/assignment here
  int* p;
};

int main() {
    Object a(5);
    goto Label;
    Object b(6);
  Label:
    cout << *a.p << endl;
}

Now, you never actually use bp , so it looks like the fact that you skipped the initialization is no big deal. ap was properly initialized, so this will output 5 , no problem. But then you return from main , and destructors start being called... including b.~Object() , which calls delete p; . But bp was never initialized, so who knows what that line will do?

In these cases, I believe the code is actually ill-formed, and the compiler is required to reject it. It appears that instead of outright rejecting it, the compiler is chosing to warn you about the possible issues, so that you can decide for yourself if there is a concern.

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