C4533警告:为什么goto跳过变量初始化

C4533 warning: why does goto skip variable initialization?

本文关键字:变量 初始化 goto 警告 为什么 C4533      更新时间:2023-10-16

我得到了:

警告C4533: 'b'的初始化被goto FreeDC跳过。

但是如果代码到达WM_CREATE中的标签FreeDC,则不初始化' b '。如果在这种情况下没有初始化,它的初始化是如何被跳过的。我只是不明白这个警告。

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

如果您使用goto,则不会调用b的构造函数,但它仍然在作用域中。从技术上讲,这是一个错误,尽管有些编译器只发出警告。

下面是一个例子:

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

看起来好像你没有使用b,但是它的析构函数仍然被调用:

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
}

您可以通过引入合适的局部作用域来避免这个问题,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;

如果您仔细考虑c++、作用域和自动对象生命周期,您将得出结论,goto 确实对整个编程模型造成了严重破坏。这就是为什么有很多(通常暗含的)条件决定你可以去哪里,不可以去哪里。通常,如果作用域包含新的自动变量,那么跳转到作用域的中间是有问题的。我们通过引入goto跳转完全跳过的新的局部作用域来避免这种情况。

我真的不知道,但是当if语句足够时,为什么要使用goto呢?

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.

考虑一个较小的、平凡的测试用例:

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

在最后一行,a.i显然是5。但是b.i的值是多少呢?当创建该对象时,它应该初始化为6,但是您显式地告诉程序跳过这一行。可以是任何东西

现在,我们假设Object是一个更有用的类型:

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

现在,您从未实际使用b.p,因此跳过初始化的事实看起来没什么大不了的。a.p已正确初始化,因此这将输出5,没有问题。但是当你从main返回时,析构函数开始被调用……包括b.~Object(),它调用delete p;。但是b.p从来没有初始化过,所以谁知道那一行会做什么呢?

在这些情况下,我认为代码实际上是错误的,编译器需要拒绝它。编译器似乎没有直接拒绝它,而是选择警告您可能存在的问题,以便您可以自己决定是否存在问题。

你不能用gotoswitch[*]跳过对象的初始化(这适用于用户定义类型以及像int s这样的基本类型)。在你的情况下,你没有使用你跳过初始化的对象,所以最好的解决方案是通过限制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);

[*]所以这些是非法的:

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

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

如果y是引用、常量或具有非平凡构造函数的用户定义对象,这一点尤为重要。

标准在6.7/3:

可以转移到块中,但不能以以下方式通过初始化绕过声明。跳跃式程序从具有自动存储持续时间的局部变量所在的点不在作用域内到它在作用域内的一点是病态的,除非变量具有POD类型(3.9),并且声明时没有初始化式(8.5) .