问候语!我叫乔恩·斯特金;我是微软前沿团队的开发人员。我很高兴能够为Visual C++团队博客做贡献 客座博主 ”.
编写C++代码时,我的一个爱好就是尽可能多地在开发过程中尽早发现bug;很明显,bug发现得越早,修复起来就越容易,也越便宜。如果能在代码生成之前找到并纠正它们,每个人都会赢。
编译器警告级别
我假设每个人都熟悉VisualC++编译器中的编译器警告级别。它通过/W命令行开关和IDE中C/C++项目的属性页中的“警告级别”项公开。
上面显示的属性页的相关部分是针对VisualStudio 2010中创建的全新C++项目。如您所见,默认情况下,警告级别设置为/W3。但是,我建议您将所有代码编译为警告级别4,因为警告“噪波”的增加可能非常小,并且有些有用的警告仅在启用/W4时才处于活动状态。许多团队的最佳实践是确保所有代码在启用/W4的情况下干净地编译,并且通常还指定了将所有警告视为错误的相关选项(/WX),以确保在未首先解决编译器标识的所有警告之前无法提交代码。
/墙
一个不那么广为人知的警告选项是 /墙 ,如下IDE所示:
此选项(显然)启用 全部的 编译器中的警告。这当然会导致什么是警告的问题 不 指定/W4时启用?答案可以在 本页 在MSDN上,列出编译器中所有“默认关闭”的警告。我不打算浏览这个列表中的每一个警告,但是您可以看到其中许多都是非常琐碎和“嘈杂”的警告,您确实不太可能希望在任何实际的代码基上启用这些警告。例如, C4061型 (“switch语句中的枚举器不是由case标签显式处理的”)不太可能识别出任何实际的bug,如果默认情况下没有关闭它,您可能会很快地将其禁用。
但是,当您浏览列表时,您可以看到 一些 其中的警告可能会识别出潜在的严重问题。我最喜欢的几个虚拟函数: C4263型 (成员函数不重写任何基类虚拟成员函数)和 C4264型 (虚拟成员函数没有可用的重写;函数被隐藏)。在这两种情况下,我都想知道我的代码何时触发警告,因为很可能存在一个基本的bug,至少需要进行调查。
/墙 在现实世界里
因为在现实世界的代码库上启用/Wall开关是不现实的,因为所有的噪音都来自于琐碎的警告,有没有一种方法可以获得这些更有用的警告的好处,而不必忍受那些不太有用的警告的痛苦?是的,这是我过去处理这个问题的方法:
- 创建代码库中每个项目都可以包含的头文件。
- 通过使用 /FI编译器选项 .
- 添加 #pragma警告(禁用: <警告编号> ) 行到头文件,以禁用为您的代码库只产生噪声的单个警告。
- 打开代码库的/Wall开关。
或者,您可以使用相同的技术有选择地启用个别默认关闭编译器警告,并在不添加/Wall开关的情况下重新生成。但是,我更喜欢第一种方法,因为这样我就可以明确地选择要禁用哪些警告。此外,编译器团队在未来版本中添加的任何未来警告都将在编译器更新时自动启用,此时我可以决定是否有必要保持启用它们。
处理外部头文件
当您尝试在默认情况下启用许多关闭警告的情况下构建代码库时,您可能会发现,一些编译器/SDK提供的头文件编译不干净。我知道VisualC++和Windows SDK团队非常擅长确保所有标准的头标都会在警告级别4下编译干净,但是在启用/墙壁时没有这样的保证。因此,如果您想获得启用某些警告的构建的好处,则需要解决此问题。不幸的是,到目前为止,我发现最好的选择是将有问题的头文件包装成自己的头文件,这样可以暂时禁用有问题的警告,并安排代码包含这些警告。例如,如果代码使用ATL,则可能需要使用类似于以下内容的标头:
#pragma warning(push) #pragma warning(disable:4265) #pragma warning(disable:4625) #pragma warning(disable:4626) #include <atlbase.h> #pragma warning(pop)
此头文件暂时禁用atlbase.h可以生成的三个默认关闭警告,包括头文件,然后重新启用这些警告,以便它们在代码中再次处于活动状态。如果您可以将这个小头文件安排在任何系统头之前在您的代码库的include路径中找到,那么您可以将它命名为atlbase.h,这样就不必修改任何实际源代码的include指令。
假阳性
总是存在带有编译器警告的假阳性情况,对于off默认警告也没有什么不同。您必须对要启用的每个警告使用您的判断,以查看在代码库中生成的误报数是否超过捕获真正bug的可能性。我认为值得遭受一些误报的一个例子是 C4265型 –类具有虚函数,但析构函数不是虚函数。此警告在启用时由以下代码触发:
class Animal { public: Animal(); ~Animal(); //not virtual virtual void MakeNoise(); };
如您所见,该类有一个虚函数,但没有声明析构函数 事实上的 . 如果希望在堆上创建实现Animal基类的对象,然后通过Animal*类型的指针删除这些对象,那么这可能是一个真正的bug,可能会导致内存泄漏。或者,如果这些对象具有其他销毁机制,例如所有COM接口都使用的virtual IUnknown::Release()方法,则这可能是误报(即没有问题)。因此,如果您选择在启用此警告的情况下构建代码,那么在实现COM接口的任何代码中都会出现误报警告。在这一点上,您必须决定警告是否可能捕获代码中的真正bug;如果要保持警告处于启用状态,则必须使用上述atlbase.h示例中所示的#pragma warning方法在假阳性位置周围临时禁用它。
摘要
综上所述,以下是我的建议:
- 如果你还没有建立(干净)在警告级别4,从今天开始!
- 考虑使用上面的技术来启用一些off-by-default编译器警告
首先,我会考虑在默认情况下启用这些关闭警告:
- C4191型 –从“表达式类型”到“所需类型”的不安全转换
- C4242型 –从“type1”转换为“type2”,可能会丢失数据
- C4263型 –成员函数不重写任何基类虚拟成员函数
- C4264型 –基类“class”中的虚拟成员函数没有重写可用;函数被隐藏
- C4265型 –类具有虚函数,但析构函数不是虚函数
- C4266型 –基“type”中的虚拟成员函数没有可用的重写;函数被隐藏
- C4302型 –从“类型1”截断为“类型2”
- C4826型 –从“type1”到“type2”的转换是符号扩展的。这可能会导致意外的运行时行为
- C4905型 –宽字符串文字转换为“LPSTR”
- C4906型 –字符串文字转换为“LPWSTR”
- C4928型 –非法复制初始化;已隐式应用多个用户定义的转换
如果您在代码中启用了这些警告,它们可以帮助您找到真正的bug,我很乐意在评论中听到它们。
最后,我要感谢VisualC++团队让我在博客上留言。谢谢!