UBSAN 报告:-875 << 7 为未定义行为

UBSAN reports: -875 << 7 as undefined behaviour

简单的代码片段:

#define FOO 7
int bar = -875;
bar <<= FOO;

UBSAN 将此报告为 UB。

我的理解是 -875 << 7 就是 -(875<<7) 而且没有溢出。

那么,这里真的有问题吗?

您的理解有误。

首先你使用了bar <<= FOO语法。这明确地转移了 bar 并且 bar 是负数。负值的左移会在 C 中产生未定义的行为。bar <<= FOO 不可能被解释为 -(875<<7)

其次,关于-875 << 7在运算符优先级方面:一元运算符总是比二元运算符具有更高的优先级,这意味着-875 << 7(-875) << 7而不是-(875 << 7) .同样,负值的左移会在 C 中产生未定义的行为。

在符号量级机器上,尚不清楚左移负数的效果应该是什么,如果尝试这样的操作,这样的机器陷入困境也不是没有道理的。在这样的机器上,对负整数左移的行为施加任何要求可能需要此类机器的编译器生成额外的代码,即使在要移位的值始终为正的情况下也是如此。为避免强加此类成本,该标准的作者拒绝强制执行任何特定行为。

个数补码和二进制补码平台在移动负值时没有逻辑理由陷入困境(尽管 -1<<1 是否应该在个数补码机器上产生 -2 或 -3不清楚),但标准的作者认为没有理由说负值的左移在使用符号幅度整数的平台上具有未定义的行为,在使用补码的平台上具有实现定义的行为,以及标准定义的使用二进制补码的平台上的行为,因为任何二进制补码实现都会将 -1<<1 视为产生 -2,无论标准是否强制要求,除非作者故意迟钝.

直到大概 2005 年左右,对于只能在普通的二进制补码机器上使用左移运算符对负值调用 运行 的代码,甚至都没有想象中的不安全。不幸的是,大约在那个时候,一个想法开始流行起来,它表明一个编译器避免做任何标准没有强制要求的事情可能比一个在标准没有强制要求的情况下表现有用的编译器更好 "efficient",并且这样的"efficiency" 是可取的。我还不知道编译器将语句 y=x<<1; 视为追溯性地使 x 的值非负,但我不相信有任何理由相信他们赢了以后不要这样做,除非或直到某个机构正式编纂行为保证,主流微型计算机 C 编译器一致维护了 25 年以上,这样的代码不能被视为 "safe"。