C4533警告:为什么goto跳过变量初始化
C4533 warning: why does goto skip variable initialization?
我得到了:
警告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
从来没有初始化过,所以谁知道那一行会做什么呢?
在这些情况下,我认为代码实际上是错误的,编译器需要拒绝它。编译器似乎没有直接拒绝它,而是选择警告您可能存在的问题,以便您可以自己决定是否存在问题。
你不能用goto
或switch
[*]跳过对象的初始化(这适用于用户定义类型以及像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) .
- 为什么C++有不同的变量初始化方式?
- 静态 constexpr 成员变量初始化
- C++不同的变量初始化
- 全局和局部变量初始化与 constexpr 的差异背后的基本原理
- 是变量初始化失败吗?
- 视觉C++:在 DLL 加载期间,全局变量初始化顺序是否具有确定性?
- 类静态变量初始化顺序
- 使用 constinit 变量初始化 constexpr 变量
- 是否可以在不修改父类的情况下将成员变量初始化推迟到继承的类?
- 使用全局变量初始化不同编译单元中的其他全局变量
- 使用默认构造函数引用成员变量初始化错误
- 宏的 if 语句中的变量初始化
- 不稳定的C :每行适应性变化多变量初始化
- 同一函数中的静态函数变量初始化顺序
- C 语言中的静态变量初始化
- 错误:调用'begin(long double [nPoints])'没有匹配函数;使用硬编码的 int 与整数变量初始化向量
- 类POD成员变量初始化
- 尝试捕获类变量初始化的范围
- 共享库中 __attribute__((构造函数)) 的全局/静态变量初始化问题
- 多变量初始化编译器支持