破警理论

Перевод статьи на русском

null

“这个” 破警理论 是一个虚构的理论规范设置和信号效果的编码实践和错误检查技术在3 研发 党的图书馆对新的错误和设计反模式。该理论指出,维护和监控警告级别以防止诸如“有符号/无符号不匹配”、“逗号前无效果”和“使用的非标准扩展”等小问题,有助于营造秩序和合法性的氛围,从而防止更严重的错误(如缓冲区溢出)的发生。

Visual Studio 2017版本15.6预览版1 提供了新的方法,使您的代码更加健壮和无警告。

不过,撇开玩笑不谈,并不是所有的警告都是平等的:

  • 有些是精确的
  • 有些是有用的
  • 有些是可以采取行动的
  • 有些很快就能被发现
  • 有些对现有的代码库几乎没有影响

事实上,没有人拥有所有这5个“宜人”的特征,因此,一个特别的警告通常会落在这些特征的某个地方,从而引发无休止的讨论,哪些应该或不应该被报道。当然,不同的团队会根据不同的标准来确定应该发出哪一组警告,而编译器开发人员则会尝试将它们放入某种过度近似的分类法中,以满足这些众多的标准。Clang和GCC试图通过使用警告族来提高细粒度,MSVC则通过使用警告级别来提高粗粒度。

在我们的 诊断改进调查 在270名受访者中,有15%的人表示他们使用 /Wall /WX 表明他们对任何警告都是零容忍的。另外12%的人表示他们用 /Wall ,这意味着 /W4 默认情况下启用所有关闭警告。另一个30%%的版本 /W4 . 这些是不相交的组,总共有57%的用户对代码的要求比Visual Studio IDE的默认要求更严格- /W3 或者编译器本身- /W1 . 这些水平有些武断,绝不代表我们自己的做法。例如,MSVC图书馆团队努力使我们所有的图书馆 /W4 干净。

虽然每个人都不同意应该报告警告的哪一个子集,但大多数人都同意,在项目中应该有0个来自商定集合的警告:所有警告都应该被修复或抑制。一方面,0使任何新的警告成为 JND公司 臭名昭著的 韦伯-费希纳定律 但在另一方面,它在跨平台代码中经常是必需的,在跨平台代码中,它被反复使用 报道 一个平台/编译器上的警告通常会表现为错误或更糟的情况——另一个平台/编译器上的bug。这种对警告的零容忍可以很容易地对内部代码强制执行,但对外部代码3实际上是不可强制执行的 研发 -党的图书馆,其作者可能已经确定了一套不同的[在]可以容忍的警告。要求所有库对所有已知警告保持干净既不切实际(由于误报和没有标准符号来抑制它们),也不可能实现(因为所有警告的集合是一个不断增长的目标)。后者是编译器和库生态系统共同进化的结果,其中一个方面的改进需要改进,从而跟上另一个方面的竞争。由于这种协同进化,开发人员经常要处理的编译器没有赶上他们的库,或者库没有赶上他们的编译器,而且这两个库都不在开发人员的控制之下。在这种情况下,开发人员认为,所有的开发人员都使用了一个生动活泼的语言,比如C++,实际上希望拥有 控制他们无法控制的代码中警告的发出 .

我们提供了一个新的编译器开关组: /external:* 处理“外部”标题。我们选择了 外部收割台 “结束” 系统头 其他编译器使用,因为它更好地代表了3的多样性 研发 现有的党的图书馆。此外,该标准在[lex.header]中已经引用了外部头文件,所以这是很自然的。我们定义一个组,而不仅仅是新的开关 便于用户发现 ,可以根据他们已经知道的开关猜出开关的完整语法。目前,该组由5个交换机组成,分为2类(以下各节分别介绍):

定义外部标头集的开关

  • /external:I <path>
  • /external:anglebrackets
  • /external:env:<var>

在外部标头上定义诊断行为的开关

  • /external:W<n>
  • /external:templates-

2号 组以后可能会扩展到 /external:w, /external:Wall, /external:Wv:<version>, /external:WX[-], /external:w<n><warning>, /external:wd<warning>, /external:we<warning>, /external:wo<warning> 等构成相应的 警告开关 当应用于 外部的 (相对于用户)头或任何其他开关时,它将有意义的专门化为外部头。请注意,由于这是一个实验性的功能,您将不得不另外使用 /experimental:external 切换以启用该功能,直到完成其功能。让我们看看那些开关是做什么的。

外部标头

我们目前为用户和库作者提供了4种定义外部头的方法,这些方法在添加到构建脚本、侵入性和控制的容易程度上有所不同。

  • /external:I <path> –GCC、Clang和EDG中的-isystem或-i(小写)的道德等价物,定义哪些目录包含外部头。该路径的所有递归子目录也被认为是外部的,但只有路径本身被添加到搜索包含的目录列表中。
  • /external:env:<var> –指定环境变量的名称,该环境变量包含以分号分隔的带有外部标头的目录列表。这对于依赖于INCLUDE和CAExcludePath等环境变量的构建系统非常有用,可以指定外部INCLUDE和那些不应该由其分析的外部INCLUDE的列表 /analyze 分别。用户只需使用 /external:env:INCLUDE /external:env:CAExcludePath 而不是通过 /external:I 开关。
  • /external:anglebrackets –允许用户处理通过 #include <> (相对于 #include "" )作为外部标头
  • #pragma system_header –一种侵入式头标记,允许库编写器将某些头标记为外部头。

外部标头的警告级别

基本思想 /external:W<n> 开关用于定义外部标头的默认警告级别。我们用一个道德标准来包装这些内容:

#pragma warning (push, n)
// the global warning level is now n here
#pragma warning (pop)

结合您首选的定义外部标头集的方法, /external:W0 您需要做的一切就是完全关闭从这些外部标题发出的任何警告。

例子:

外部标题:some_lib_dir/some_hdr.hpp

template <typename T>
struct some_struct
{
    static const T value = -7; // W4: warning C4245: 'initializing': conversion from 'int' to 'unsigned int', signed/unsigned mismatch
};

用户代码:myu prog.cpp

#include "some_hdr.hpp"

int main()
{
    return some_struct<unsigned int>().value;
}

将此代码编译为:

cl.exe /I some_lib_dir /W4 my_prog.cpp

将在标题内发出4级C4245警告,如注释中所述。运行时使用:

cl.exe /experimental:external /external:W0 /I some_lib_dir /W4 my_prog.cpp

没有效果,因为我们还没有指定外部头是什么。同样,将其运行为:

cl.exe /experimental:external /external:I some_lib_dir /W4 my_prog.cpp

也没有效果,因为我们没有指定外部标头中的警告级别,并且默认情况下,它与/W开关中指定的级别相同,在本例中为4。要抑制外部头中的警告,我们需要同时指定哪些头是外部头以及这些头中的警告级别:

cl.exe /experimental:external /external:I some_lib_dir /external:W0 /W4 my_prog.cpp

这将有效地清除某些u hdr.hpp中的任何警告,同时保留我的u prog.cpp中的警告。

跨越内部/外部边界的警告

如果这样做不会隐藏一些用户可操作的警告,那么简单地设置外部头的警告级别就足够了。只执行pragma push/pop-around include指令的问题是,它有效地关闭了在源自用户代码的模板实例化上发出的所有警告,其中许多警告可能是可操作的。这样的警告可能仍然表明用户代码中的问题只发生在具有特定类型的实例化中(例如,用户忘记应用类型trait removing const或&),并且用户应该知道这些问题。在此更新之前,在warning的程序点上有效的警告级别的确定完全是词汇性的,而导致该警告的原因可能来自其他范围。有了模板,在实例化点设置的警告级别应该在哪些警告是允许的,哪些是不允许的方面发挥作用,这似乎是合理的。

为了避免在定义恰好在外部标头中的模板中隐藏警告,我们允许用户通过传递消息从确定给定程序点警告级别的简化逻辑中排除模板 /external:templates- 随着 /external:W<n> . 在这种情况下,我们不仅要查看定义模板和发生警告的程序点的有效警告级别,还要查看模板实例化链中每个程序点的适当警告级别。我们的警告级别相对于在每个级别发出的消息集形成了一个格子(这并不是一个完美的格子,因为我们有时会在多个级别发出警告)。在给定的程序点上,关于这个格应该允许什么样的警告,有一种过于近似的做法,就是在实例化链上的每个程序点上允许的消息的并集,这正是传递的消息 /external:template- 做。使用此标志,您将能够看到来自外部头的警告,只要这些警告是从模板内部发出的,并且模板是从用户(非外部)代码内部实例化的。

cl.exe /experimental:external /external:I some_lib_dir /external:W0 /external:templates- /W4 my_prog.cpp

这将使外部标头内的警告重新出现,即使该警告位于警告级别设置为0的外部标头内。

禁止和强制执行警告

上述机制本身并不启用或禁用任何警告,它只为一组文件设置默认警告级别,因此所有现有的启用、禁用和抑制警告的机制仍然有效:

  • /wdNNNN, /w1NNNN, /weNNNN, /Wv:XX.YY.ZZZZ 等。
  • #pragma warning( disable : 4507 34; once : 4385; error : 4164 )
  • #pragma warning( push[ ,n ] ) / #pragma warning( pop )

除了这些,什么时候 /external:templates- 使用时,我们允许在实例化点抑制警告。在上面的示例中,用户可以显式抑制由于使用 /external:templates- 具体如下:

int main()
{
    #pragma warning( suppress : 4245)
    return some_struct<unsigned int>().value;
}

在开发人员连续体的另一方面,库作者可以使用完全相同的机制来强制执行某些警告,或者在某个级别执行所有警告,如果他们觉得这些警告永远不应该被压制 /external:W<n> .

例子:

外部标题:some_lib_dir/some_hdr.hpp

#pragma warning( push, 4 )
#pragma warning( error : 4245 )

template <typename T>
struct some_struct
{
    static const T value = -7; // W4: warning C4245: 'initializing': conversion from 'int'
                               // to 'unsigned int', signed/unsigned mismatch
};

#pragma warning( pop )

通过对库标题的上述更改,库的所有者现在可以确保其标题中的全局警告级别为4,而不管用户在中指定了什么 /external:W<n> 因此,所有4级及以上的警告都将发出。此外,像在上面的示例中一样,她可以强制执行某个警告将始终被视为错误、禁用、抑制或在其报头中发出一次,并且,再次,用户将无法覆盖该故意选择。

在当前的实现中,当编译器的后端(与前端相反)发出警告时,您仍然会偶尔从外部头中获得警告。这些警告通常以C47XX开始,但并非所有C47XX警告都是后端警告。一个好的经验法则是,如果对给定警告的检测可能需要数据或控制流分析,那么在我们的实现中很可能是由后端完成的,并且当前机制不会抑制这样的警告。这是一个已知的问题,正确的修复可能要等到visualstudio的下一个主要版本,因为它需要对我们的中间表示进行突破性的更改。您仍然可以使用/wd47XX以传统方式禁用这些警告。

除此之外,这个实验特性还没有被集成到 /analyze 警告,因为我们试图收集一些用户的反馈第一。 /analyze 警告没有警告级别,因此我们也在研究将它们与当前逻辑集成的最佳方法。

我们目前还没有使用此功能的指导 SDL合规性 ,但我们将与SDL团队联系,以提供此类指导。

回到与破窗理论的类比,我们对这个特性对更广泛的图书馆生态系统的净影响有着复杂的感觉。一方面,它将用户置于“不是我的问题”模式,使他们不太可能向上游报告或修复问题,从而对图书馆作者造成伤害。另一方面,它让他们对自己的代码有了更多的控制,因为他们现在可以通过制服过去阻止这种强制执行的流氓库来对代码执行更严格的要求。

虽然我们同意此功能的次要影响可能会限制对库的贡献,但考虑到用户正在处理的代码,解决上游问题通常不是用户的首要任务,但修复自己代码中的问题是她最优先考虑的事情,而来自其他库的警告阻碍了对其中警告的检测,因为她不能只在自己的代码上强制执行/WX。更重要的是,我们相信这将产生第三效应,平衡第二效应的净损失。

使开发人员能够从3 研发 党库警告我们鼓励她专注于自己的代码-使它更干净,甚至可能警告免费在尽可能大的水平,她可以。三 研发 党库开发人员也是这个链中的开发人员,因此通过允许他们从3 研发 第三方依赖,我们鼓励他们清理他们的代码,并使其尽可能大的警告级别编译,等等,为什么这是重要的?本质上, 在当前的世界中,警告大量出现在整个库依赖链上 你在这个链条上走得越远,就越难对它们做些什么——开发人员感到不知所措,放弃了任何这样做的尝试。另一方面,在这个世界上,我们可以区分我们自己的代码和3 研发 政党代码, 链中的每个开发人员都有办法阻止(阻止)雪崩的影响,并鼓励将其影响降至最低,从而将对整个链的总体影响降至最低 . 这当然是一种推测,但我们认为这和我们所担心的次要影响一样可信。

最后,我们想邀请您自己尝试一下这个功能,让我们知道您的想法。请告诉我们你喜欢什么和不喜欢什么,否则少数人可能会替你决定。此功能从开始提供 Visual Studio 15.6预览版1 . 一如既往,我们可以通过以下评论和电子邮件联系到您( visualcpp@microsoft.com )您可以通过帮助提供反馈-> 报告产品中的问题 ,或通过 开发者社区 . 你也可以在Twitter上找到我们( @视觉 )还有Facebook( msftvisualcpp软件 ).

谢谢你 罗伯特·舒马赫 因为他指出了 破窗理论 !

© 版权声明
THE END
喜欢就支持一下吧,技术咨询可以联系QQ407933975
点赞0 分享