左移和右移负整数定义行为吗?

问题描述 投票:0回答:3

我知道,右移负号类型取决于实现,但是如果我执行左移怎么办?例如:

int i = -1;
i << 1;

这个定义明确吗?

我认为标准没有提到带符号类型的负值

如果 E1 具有符号类型和非负值,并且 E1 × 2E2 是 可以用结果类型表示,那么这就是结果值; 否则,行为是未定义的。

它只是澄清,如果结果不能用有符号类型表示,那么行为是未定义的。

c++ undefined-behavior bit-shift
3个回答
30
投票

您没有正确阅读该句子。标准将其定义为: 左操作数具有有符号类型 并且 非负值 并且 结果是可表示的(并且之前在同一段落中将其定义为无符号类型)。在所有其他情况下(请注意该句子中使用分号),即,如果这些条件中的任何一个未得到验证,则行为未定义。


25
投票
当 C 标准被编纂后,不同的平台在左移负整数时会做不同的事情。对于其中一些,该行为可能会触发特定于实现的陷阱,其行为可能超出程序的控制范围,并且可能包括随机代码执行。尽管如此,为此类平台编写的程序可能会利用此类行为(例如,程序可以指定用户在运行系统之前必须执行某些操作来配置系统的陷阱处理程序,但程序随后可以利用适当配置的陷阱处理程序)。

C 标准的作者不想说,必须修改负数左移会捕获的机器的编译器以防止此类捕获(因为程序可能潜在地依赖它),但是如果左移负数允许数字触发陷阱,该陷阱可能导致任何任意行为(包括随机代码执行),这意味着允许左移负数执行任何操作。因此是未定义的行为。

实际上,直到大约 5 年前,99% 以上为使用补码数学的机器编写的编译器(意味着 1990 年以来制造的 99% 以上的机器)都会始终为

x<<y

x>>y
 产生以下行为,在某种程度上,对这种行为的代码依赖被认为并不比假设 
char
 为 8 位的代码更不可移植。 C 标准没有强制要求这种行为,但任何想要与现有代码的广泛基础兼容的编译器作者都会遵循它。

    如果
  • y
     是有符号类型,则对 
    x << y
    x >> y
     进行评估,就好像 
    y
     被转换为无符号一样。
  • 如果
  • x
     是类型 
    int
    ,则 
    x<<y
     相当于 
    (int)((unsigned)x << y)
  • 如果
  • x
    int
     类型且为正,则 
    x>>y
     相当于 
    (unsigned)x >> y
    。如果 
    x
     属于 
    int
     类型且为负,则 
    x>>y
     相当于 
    ~(~((unsigned)x) >> y)
  • 如果
  • x
     属于 
    long
     类型,则适用类似的规则,但使用 
    unsigned long
     而不是 
    unsigned
  • 如果
  • x
     是 N 位类型并且 
    y
     大于 N-1,则 
    x >> y
    x << y
     可以任意产生零,或者可以表现为右侧操作数是 
    y % N
    ;它们可能需要与 
    y
     成正比的额外时间 [请注意,在 32 位机器上,如果 
    y
     为负数,则可能需要很长的时间,尽管我只知道一台机器实际上会运行更多时间超过 256 个额外步骤]。编译器的选择不一定一致,但总是会返回指示值之一,而没有其他副作用。
    不幸的是,由于我不太明白的原因,编译器编写者决定,编译器不应允许程序员指示编译器应使用哪些假设来删除死代码,而应假设不可能执行任何未强制执行其行为的转换按C标准。因此,给出如下代码:
uint32_t shiftleft(uint32_t v, uint8_t n) { if (n >= 32) v=0; return v<<n; }

编译器可能会确定,因为当 n 为 32 或更大时,代码将出现未定义行为,所以编译器可能会假设
if
永远不会返回 true,因此可能会省略代码。因此,除非或直到有人提出一个 C 标准来恢复经典行为并允许程序员指定哪些假设值得删除死代码,否则不能为任何可能输入超现代编译器的代码推荐这种构造。



3
投票

这包括

如果 E1 具有符号类型

和非负值

© www.soinside.com 2019 - 2024. All rights reserved.