"无需诊断"的理由是什么?

Nia*_*all 14 c++ language-lawyer c++11

大多数人都熟悉C++中的"未定义"和"未指定"的行为记录,但是"不需要诊断"呢?

我注意到这个问题和答案,涉及形成不良的程序,但没有详细说明"无需诊断"声明的根源.

委员会在将某些内容归类为"无需诊断"时采用的一般方法是什么?

  • 标准委员会指定错误的错误有多严重?
  • 这些错误是否几乎不可能被发现,因此诊断?

"未定义"和"未指定"行为的例子并非短缺; 在没有ODR的情况下,"无需诊断"类型错误有哪些实际例子?

Naw*_*waz 12

我会尝试解释分类为未定义行为(UB)的行为"无需诊断".

标准通过说"UB不需要诊断" 1,使编译器完全自由地优化代码,因为编译器只能通过假设您的程序完全定义来消除许多开销(这意味着您的程序没有UB) )这是一个很好的假设 - 毕竟如果这个假设是错误的,那么编译器基于这个(错误的)假设所做的任何事情都会以一种未定义的(即不可预测的)方式运行,这种方式是完全一致的,因为你的程序还是有不确定的行为!

请注意,包含UB的程序可以像任何行为一样自由.再次注意,我说"一致",因为它符合标准的立场:"语言规范和编译器都不保证程序的行为,如果它包含UB(s)".

1. 相反的是"需要诊断",这意味着编译器需要通过发出警告错误消息向程序员提供诊断.换句话说,它允许承担程序明确定义,以便优化的代码的某些部分.

这是一篇文章(在LLVM博客上),它使用示例进一步解释了这一点:

除了文章(斜体矿):

有符号整数溢出:如果'int'类型的算术(例如)溢出,则结果是未定义的.一个例子是"INT_MAX + 1"不能保证是INT_MIN.此行为允许某些类别的优化对某些代码很重要.例如,知道未定义INT_MAX + 1允许将"X + 1> X"优化为"真".知道乘法"不能"溢出(因为这样做将是未定义的)允许将"X*2/2"优化为"X".虽然这些看似微不足道,但这些事情通常都是通过内联和宏观扩展来揭示的.这允许的更重要的优化是"<="循环,如下所示:

for (i = 0; i <= N; ++i) { ... }
Run Code Online (Sandbox Code Playgroud)

在这个循环中,如果"i"在溢出时未定义,编译器可以假设循环将完全迭代N + 1次,这允许广泛的循环优化启动.另一方面,如果变量被定义为在溢出时回绕,然后编译器必须假定循环可能是无限的(如果N是INT_MAX则会发生) - 然后禁用这些重要的循环优化.这特别影响64位平台,因为很多代码使用"int"作为归纳变量.

值得注意的是,无符号溢出保证定义为2的补码(换行)溢出,因此您可以始终使用它们.定义有符号整数溢出的成本是这些优化简单地丢失了(例如,常见的症状是64位目标上的循环内部的大量符号扩展).Clang和GCC都接受"-fwrapv"标志,该标志强制编译器按照定义处理有符号整数溢出(除了INT_MIN除以-1).

我建议你阅读整篇文章 - 它有三个部分,都很好.

希望有所帮助.

  • @Niall:实际上,现在可能已经过时了.来自谷歌的人已经在ASAN(Address Sanitizer),MSan(Memory Sanitizer)和TSan(Thread Sanitizer)系列中将UBSan集成到Clang中(Undefined Behavior Sanitizer).每个清理程序都会对编译时假定的内容进行运行时检查(它们不一定要混合在一起,您可能需要使用不同的构建来完成所有这些操作).顺便说一下,这些支票中的一些(全部?)也被移植到了gcc.对于完整的UBSan,新选项应为`-fsanitize = ...`,```等于`undefined`. (5认同)

Joh*_*itb 12

这里有一个讨论:https://groups.google.com/a/isocpp.org/forum/#!topic/ std- discussion / lk1qAvCiviY,各个委员会成员的发言.

普遍的共识似乎是

  • 没有规范性的区别
  • 形成不良; 不需要诊断仅用于编译时规则违规,从不用于运行时规则违规.

正如我在那个帖子中所说,我曾经在一次讨论中听过(我不记得在哪一个,但我确信有相关的有见地的委员会成员)

  • 形成不良; 没有诊断所需的明确是坏的违反规则和原则上可以在编译时诊断的病例,但需要从实现的巨大努力.
  • 对于实现可以找到有用含义的事物的未定义行为,所以不一定是纯粹的邪恶,并且对于任何导致任意后果的运行时违规.

对我来说粗略的指南是; 如果它在编译时,它往往是"格式错误;不需要诊断",如果它在运行时,它总是"未定义的行为".