下面的代码片段选自MFC库。该错误被PVS-Studio诊断为:V301 意外的函数重载行为。观察函数'WinHelpW'的第一个参数,这个函数在派生类'CFrameWndEx'和基类'CWnd'中。
解释
当你重写一个虚函数的时候,很容易在函数签名的时候出错,也很容易变成定义一个新的函数,这个函数跟基类里的虚函数没有任何关联。在这个例子中,有这几类错误:
在被重写的函数中,参数类型不同。
在被重写的函数中,参数个数不同。在参数比较多的时候,这种情况会更严重。
重写的函数在const修饰符这里不同。
基类函数不是虚函数。这种情况是以为在派生类中的函数会重载基类中的函数,但事实上,并没有,派生类隐藏了它。
相同的错误也会在修改已存在的代码中的参数类型或参数个数的时候发生。当程序员改变要改变虚函数签名的时候,他可能改了几乎所有有继承关系的类里的,但忘了在一些派生类中改,于是,就出错了。
在将代码移植到64位平台的时候,这种错误出现得更平凡。因为这个时候要把DWORD改成DWORD_PTR , LONG , LONG_PTR等等。细节. 这就是我们这个例子中发生的错误。
即使有这种错误的代码能够在32位系统正常运行,在64位机子上也会出错。因为在32位上,DWORD和DWORD_PTR是unsigned long的同义词,但是在64位平台上,DWORD_PTR是unsigned __int64的同义词。
正确代码
现在有方法来避免上面描述的错误了。在C++11里添加了两个标识符:
Override——表明该函数是重写基类中的虚函数。
Final——表明该函数在派生类中无需重写。
我们对override这个标识符比较感兴趣。这个标识符可以指示编译器去检查某个虚函数是不是重写基类里的函数,如果不是,就报错。
如果在写CFrameWndEx 类里的WinHelp函数的时候我们有用override指明,这个应用的64位版本在编译的时候会出错。所以,这个错误应该在早期清除掉。
在重写虚函数的时候记得用override(或final)标识符。更多关于override和final的知识请看下面的文章:
Cppreference.com. override specifier (since C++11)
Cppreference.com. final specifier (since C++11)
维基百科. Explicit overrides and final.
Stackoverflow.com. 'override' in c++11.
下面的代码来自CoreCLR项目。这段危险的代码被PVS-Studio诊断为:V704 应避免'this == nullptr'这个表达式——这个表达式在新的编译器上总是false,因为‘this’指针永远不会为NULL。
解释
人们以前常拿this指针来和0/ NULL / nullptr来做比较。这种情况只有在C++开发阶段的早期是正常的。我们是在‘考古学’研究的时候发现这样的代码的。我建议阅读一篇关于checking Cfront的文章了解更多关于它们的细节。而且,在以前,this指针的值是可以改变的,但过去太久了,这件事已经被遗忘。
让我们再回过头来看this和nullptr之间的比较。
现在它是非法的。根据现代C++标准,this永远不会等于nullptr。
根据C++标准,调用IsFirstElemFieldSeq()方法来访问空指针this会导致未定义行为。
看上去它是想表示,如果this==0,则在运行这个函数的时候就不能进入该区域。但事实上,这段代码有两种完全不同的实现方式。根据C++标准,this指针用于不会为空,所以编译器会通过简化它来优化函数调用:
对了,这里还有一个陷阱。假设这里有一个多重继承:
假设Y类的大小是8字节。然后源指针NULL(0x00000000)在这样的情况下是对的,然后它又指向了下一个对象FieldSeqNode的开始处,然后你要偏移sizeof(Y)字节。所以指向IsFirstElemFieldSeq()函数的this指针应该是0x00000008。最后,检查"this == 0"也没什么意义了。
正确代码
很难给出正确代码的例子。只是把判断条件从这个函数中移除是不够的。你要重构代码,用一种你永远不会调用到使用了空指针的函数。
建议
所以,现在已经宣布"if (this == nullptr)"是不合法的了。然而你还是可以在很多应用程序和库(比如说,MFC库)中发现这条代码。这就是为什么Visual C++还在孜孜不倦地比较this和0。我猜,编译器开发者不会那么疯狂把正确工作了那么多年的代码删掉。
但这条规则已经正式通过了。所以从现在开始,让我们避免将this和null比较。还有,如果你有时间,检查一下那些非法的比较,已经重写那些代码。
很多编译器会有如下的反映。首先,它们会给出一个关于比较的警告。可能在我还没有研究这个问题的时候,它们已经给了。然后,在某一时刻,它们要支持新标准,那你的代码就不能正常运行了。所以我强烈建议你现在就开始遵循这条规则,后面也会很有用的。
P.S. 在重构的时候,你需要空对象模式。
另外两个关于这个话题的链接:
Still Comparing "this" Pointer to Null?
Diagnostic V704.
下面的代码选自NAME项目。代码中的错误被PVS-Studio诊断为:V721 VARIANT_BOOL
类使用不正确。true(VARIANT_TRUE)等于-1。检查第一个参数。
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)