【问题标题】:Is there a rule to spot out UB?有没有发现UB的规则?
【发布时间】:2023-04-01 09:40:01
【问题描述】:

我阅读了this 很棒的太宽泛的问题,并且遇到了一些我以前不知道的 UB。

我不时看到 UB 的主要原因是在两个序列点之间两次更改变量。诸如:x = x++z = y++ + ++y;。阅读 在两个序列点之间两次更改变量是 UB 帮助我了解了这些情况下的根本原因。

但是像负片移位这样的事情呢? (int x = 8 << -1) 是否有规则可以解释这一点,或者我应该将其记住为独特的 UB 可能性?

我查看了here 并在 Integer Overflows 部分下发现了带有负数的位移,但我不明白它们为什么相关。当int 移位过多时,会导致溢出,但 IMO 移位负数只是 UB,问题不在于“超出边缘”的位...

也看过这里,但没有回答我的问题:

对每个操作数执行整数提升。结果的类型是提升的左操作数的类型。如果右操作数的值为负数或大于或等于提升的左操作数的宽度,则行为未定义

所以我的问题是:

  1. 具体来说,负数移位是否被视为整数溢出?如果是,为什么?
  2. 如果不是,它是更大现象的一部分吗?
  3. 是否有(其他)独特案例不能归为一个根本原因?

【问题讨论】:

  • 负移位未定义,超长移位也是如此(N 位整数类型的 N 位或更多位移位)。标准是这样说的。你必须知道它是这么说的。是的,有很多案例,将它们分组会很棘手。 C11 标准的附件 J.2 记录了第 557-571 页上的未定义行为(每页末尾只有几行,因此略多于 14 页)。定期阅读以了解未定义的内容。不;我也没记住。
  • @JonathanLeffler 这是一个令人印象深刻的列表,谢谢!虽然我希望有一些更容易记住的东西:)
  • 回答你的问题 - 我希望一些更容易记住的东西 我的一般经验法则是 - 任何似乎会改变不同实现(目标、平台等)的行为) 是“发现” UB 的危险信号。然后我用清单确认。这是有道理的,因为标准是在抽象机器上定义的,而不是任何实现。因此,触摸实现的方面必须保持未定义。警惕实现定义的行为。
  • @Jean-BaptisteYunès 您非常有说服力的例子的一个更抽象的版本是“如果可能很难让硬件供应商同意这一点,或者很难明确定义并留出一些空间为了优化实现,那么它可能是UB”。这可能是 OP 正在寻找的“易于记忆的经验法则”。
  • UB 类型的优秀总结:blog.regehr.org/archives/1520

标签: c undefined-behavior


【解决方案1】:

具体来说,负数移位是否被视为整数溢出,如果是,为什么?

不是,因为将 0 移动任意数量都不会溢出,但将 0 的值移动负值仍然是未定义的行为。 (我假设如果您首先将移位量重新解释为无符号整数,那么您可以认为它是整数溢出,此时它将很大并且肯定超出允许的范围,并且如果将其解释为实际移位量如果移位的值非零,乘以 2 的幂肯定会溢出)。

简而言之,负数移位会产生未定义的行为,因为语言标准表明它确实如此。

如果不是,它是更大现象的一部分吗?

John Regehr 在a blog post 中给出了一些广泛的 UB 类别。转移无效金额属于“其他 UB”类别...

是否有(其他)独特案例不能归类为一个根本原因?

是的,请参阅上面的帖子。其中(直接摘自博文):

  • 减去不指向或刚好超出同一数组对象的指针 (6.5.6)。
  • 对象的存储值不是通过允许类型 (6.5) 的左值访问的
  • 非空源文件不以换行符结尾,该换行符之前不紧跟反斜杠字符,也不以部分预处理标记或注释结尾 (5.1.1.2)

您可能会以某种方式对这些和其他示例进行分类,但这取决于您希望如何做到这一点。

特别是,上面的最后一个示例(关于源文件不以换行符结尾)显示了某些规则是多么随意。

【讨论】:

  • 既然您对该内容有一个答案,我提议从我的答案中删除您的漂亮链接,以免辜负您的信任。你想要我吗?
【解决方案2】:

(编译来自 cmets 的答案,包括我的。)

找到实际未定义行为 (UB) 的一个很好的起点是Jonathan Leffler 的这些引用:

是的,有很多案例,将它们分组会很棘手。 C11 标准的附件 J.2 记录了第 557-571 页上的未定义行为(每个结尾页只有几行,因此略多于 14 页)。

参考相关文章,其中讨论了 UB 的类型、发现工具并包含 UB 列表;长且(根据作者的意图)完整(由 davmac 提供):
https://blog.regehr.org/archives/1520

“记忆”的两种方法:

  1. by Ajay Brahmakshatriya,关注不可避免的平台依赖性:

    我的一般经验法则是 - 任何似乎通过不同实现(目标、平台等)改变行为的东西都是“发现”UB 的危险信号

  2. by Yunnosch,专注于平衡标准化和优化的问题:

    如果很难让硬件供应商同意这一点,或者很难明确定义并为优化实施留出一些空间,那么它可能是 UB。

遗憾的是,所有这些“规则”都不容易应用。 检查实际标准是不方便的。 这两条经验法则是基于相当多的必要经验;您要么需要设计一些编译器和/或处理器,要么因它们之间的差异而受苦。

所以“是否有一种简单的方法来发现 UB?”的实际答案 可能只是“No.

【讨论】:

  • 我会考虑将此标记为社区答案,因为其中很大一部分不是您的话。如果您这样做,请删除顶部的括号注释。
【解决方案3】:

x<<yy 为负数的情况下,有些平台会处理类似z=x<<y 的内容,其微码相当于:

unsigned temp = x;
unsigned count=y;
while(count--)
  temp<<=1;
z=temp;

如果y 为负数,则该循环可能会运行很长时间;如果它在微码级别处理(我认为某些 Transputer 芯片就是这样),它可能会禁用中断数分钟,这可能会破坏系统的其他方面。

在大多数平台上,除了人为的场景之外,编译器可以保证x&lt;&lt;y 不会对xy 的任何值产生任何副作用,而不会产生可能毫无意义的值;事实上,编译器生成没有副作用的代码比做任何其他事情更容易。不幸的是,一些编译器作者认为他们应该寻找“聪明”的方法来利用y“不能”是负面的这一事实,从而引发任意坏的后果,而不考虑它是否真的有用,也许是错误地认为“聪明”和“愚蠢”是反义词。

【讨论】:

  • 还有一些平台,只有足够的y 位连接到移位单元以支持有意义的移位范围(即好像y 被屏蔽到x 的大小位)。
  • @TobySpeight:通常情况下,对于较大的 y 值,x&lt;&lt;y 将表现为x&lt;&lt;(y-1)&lt;&lt;1x&lt;&lt;(y &amp; numbits),但重要的是除了产生可能无意义的值之外,它们都不会产生副作用.
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-08-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多