【问题标题】:Get warning for left shifting a negative number获得左移负数的警告
【发布时间】:2019-06-29 17:31:35
【问题描述】:

我正在尝试为左移负数时未定义的行为生成警告。根据this的回答,C中负数的左移是未定义的。

E1

我试图了解为什么我没有收到此代码的警告: x << 3

gcc -Wall(9.1.0 版)

int main ()
{
    int x= -4, y, z=5;
    y = z << x;
    y = x << 3;
    return y;
}

另外,我也没有收到关于左移负数的警告 z &lt;&lt; x

【问题讨论】:

  • 为什么我没有收到此代码的警告:x &lt;&lt; 3? 因为左侧操作数 x 可以是有符号或无符号类型。标准不限制 x 只能是无符号类型,即它可以是有符号类型,即它可以包含负值。
  • 但是你确实收到y = z &lt;&lt; -4;(你的最后一句话)的警告。编译器试图提供帮助,但它不运行代码,以确定x 是否始终为负。如果x 的值是运行时的用户输入,它甚至可能知道

标签: c warnings undefined-behavior gcc-warning gcc9


【解决方案1】:

5 &lt;&lt; -4 中,GCC 9.1.0 和 Apple LLVM 10.0.1 与 clang-1001.0.46.4,针对 x86-64,发出警告消息(GCC 的“left shift count is negative”和“shift count is负”对于 LLVM-clang)。在-4 &lt;&lt; 3 中,GCC 不会发出警告,但 LLVM-clang 会发出警告(“移动负值是未定义的”)。

C 标准在这两种情况下都不需要诊断,因此编译器是否需要诊断是实现的质量问题(或者,编译器可能通过定义负值的左移来扩展 C,因此不要认为这是一个错误)。

当操作数不是常量时,如z &lt;&lt; xx &lt;&lt; 3,我们可能会推测编译器看不到操作数是否为负数,因此它不会发出警告。通常,这些表达式可能具有定义的行为:如果x 不是负数并且在合适的范围内(在前一种情况下不大于z 的宽度,并且不会大到x &lt;&lt; 3 在后者中溢出case),行为被定义(除了z &lt;&lt; x 可能溢出的可能性)。 C 标准并没有说 可能 具有负值的类型的左移,,有符号类型,是未定义的,只是带有负值的左移是未定义的。因此,当xz &lt;&lt; xx &lt;&lt; 3 等表达式中的签名类型时,编译器发出警告消息将是错误的。

当然,鉴于int x = -4 以及在该初始化与表达式z &lt;&lt; xx &lt;&lt; 3 之间没有对x 的任何更改,编译器可以推断出在这种特定情况下移位是未定义的并发出警告。这不是标准要求的,编译器不这样做只是编译器实现质量的问题。

【讨论】:

  • 我想我提到我使用了“gcc -Wall”。我确实没有提到版本。它是 9.1.0。至于,x godbolt.org/z/i34yM2
  • 另外,鉴于编译器没有警告 x
  • @abjoshi:不,没有编译器警告并不意味着您可以假设编译器不会“优化”代码。
  • 我希望我清楚我不是指通用优化。我只是指那些专门执行的那些,因为编译器假定我们的程序中不会有未定义的行为。
  • @abjoshi:没有编译器警告并不意味着编译器不会用它想要的任何东西完全替换代码中未定义的行为。
【解决方案2】:

编译器在这种情况下不发出警告的简短解释是因为它不是必需的。

标准中“未定义”的定义还明确指出“无需诊断”。这意味着该标准不需要实现来在这种情况下发出诊断。其原因是,从技术上讲,编译器可能无法在合理的时间内或(在某些情况下)检测到所有未定义行为的实例。有些情况只能在运行时检测到。编译器是复杂的代码片段,因此 - 即使人类可以轻松识别问题 - 编译器也可能无法识别(另一方面,编译器也会检测人类无法轻易发现的问题)。

当不需要诊断时,在编译器发出警告之前,有几件事必须发生 - 全部是自行决定的。

首先发生的事情是“实施质量”问题 - 供应商或编译器开发人员选择编写检测特定情况的代码和其他发出警告的代码

这两个步骤(检测一个案例并发出警告)是独立的并且完全可以自行决定 - 该标准不需要诊断,因此即使编写的代码检测到特定案例,编译器仍然不需要发出关于它的警告。实际上,即使检测到问题,编译器开发人员也可能出于各种原因选择不发出警告。一些警告是关于罕见的边缘情况,因此不值得努力发出它们。一些警告具有固有的“误报”率,这往往会导致开发人员在错误报告中向编译器供应商提出不必要的警告 - 因此编译器默认配置为不发出它们。

发出警告的下一个要求是编译器的用户必须选择显示警告。由于开发人员对供应商的大力游说 - 大多数现代编译器都是默认配置的,因此它们只发出相对少量的警告。因此,希望尽可能多地从编译器获得帮助的开发人员需要显式启用它。例如,通过在 gcc 和 clang 中使用 -Wall 选项。

【讨论】:

    【解决方案3】:

    在 C89 中为左操作数的所有整数值指定了将任何值左移小于字长的量的行为,即使负左操作数的指定行为在非-two's-complement 补码平台,或者在错误捕获平台上,如果重复乘以 2 会溢出。

    为避免要求实现以不太理想的方式运行,C99 的作者决定允许实现以他们认为合适的方式处理此类情况。 C99 标准的作者并没有尝试猜测实现可能有充分理由偏离 C89 行为的所有地方,而是推测实现将遵循 C89 行为,没有很好的理由不这样做 em>,无论标准是否强制他们这样做。

    如果标准的作者打算对左移操作的处理进行任何更改在 C89 行为有意义的情况下,则应该在已发布的基本原理文档中提及更改.什么都没有。我可以从这种遗漏中看到的唯一推论是,从“以几乎总是有意义的特定方式行事”到“如果实施者判断它是有意义的,则以这种方式行事,否则以任何其他方式处理代码实施者认为合适的时尚”没有被视为足以证明评论合理的变化。标准的作者没有强制执行某种行为这一事实绝不意味着他们没有预料到通用的二进制补码实现不会像往常一样继续处理这种转变,程序员也不应该有权获得类似的期望。

    【讨论】:

    • 这个问题没有问为什么没有定义左移一个负值或者历史是什么。它询问为什么当x 为负数时编译器不为z &lt;&lt; xx &lt;&lt; 3 发出警告消息(尽管它为z &lt;&lt; -4-4 &lt;&lt; 3 发出警告消息)。
    • @EricPostpischil:许多编译器定义了比标准描述的最低限度更多的行为。该行为是在 C89 中定义的,gcc 的作者可能认为,无论编译器是在 C89 还是 C99 模式下,都更容易按照定义的方式处理操作,而不是只按照前一种情况下的定义处理。跨度>
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-08
    • 2020-06-24
    • 2019-09-01
    • 1970-01-01
    相关资源
    最近更新 更多