简单的代码片段:
#define FOO 7
int bar = -875;
bar <<= FOO;
UBSAN 将其报告为 UB。
我的理解是-875 << 7
公正-(875<<7)
的,没有溢出。
那么,这里真的有问题吗?
简单的代码片段:
#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
is(-875) << 7
和 not -(875 << 7)
。同样,负值的左移会在 C 中产生未定义的行为。
在符号幅度机器上,尚不清楚左移一个负数的效果应该是什么,如果尝试这样的操作,这样的机器陷入陷阱也不是不合理的。在这样的机器上,对负整数左移的行为施加任何要求可能需要此类机器的编译器生成额外的代码,即使在要移位的值始终为正的情况下也是如此。为了避免强加此类成本,该标准的作者拒绝强制任何特定行为。
one's-complement 和 two's-complement 平台在移动负值时没有逻辑上的陷阱(尽管 -1<<1 是否应该在 one's-complement 机器上产生 -2 或 -3 尚不清楚),但是标准的作者认为没有理由说负值的左移在使用符号大小整数的平台上具有未定义的行为,在使用反码的平台上具有实现定义的行为,在使用反码的平台上具有标准定义的行为二进制补码,因为任何二进制补码实现都会将 -1<<1 视为产生 -2,无论标准是否强制要求它,除非作者故意钝化。
大概在 2005 年左右之前,对于仅使用负值左移运算符在普通的二进制补码机器上运行的代码,甚至没有任何可以想象的不安全性。不幸的是,大约在那个时候,一个想法开始占据主导地位,该想法表明,避免执行标准未强制执行的任何操作的编译器可能比在标准未强制执行的情况下表现有用的编译器更“有效”,并且这种“效率” "是可取的。我还不知道编译器认为该语句y=x<<1;
是追溯性地使x的值非负面的,但我不相信有任何理由相信他们将来不会这样做,所以除非或直到某些机构正式编纂主流微型计算机 C 编译器一致支持 25 年以上的行为保证,否则此类代码不能被认为是“安全的”。