13

如果我正确阅读了 C++ ISO 规范(第 5.8.2 和 5.8.3 节),负符号类型的右移是特定于实现的,而左移未定义的行为。

因此,我想在我们用 g++ 4.8.2 编译的遗留源代码中找到对有符号类型的移位操作。

不幸的是,我在手册中找不到这样的选项。例如,我可以使用“g++ -Wall -Wextra -pedantic”编译此代码而不会发出警告:

int si    = -1;
int left  = si << 1; // -2 (multiplication by 2, sign is preserved)
int right = si >> 1; // -1 (no change, only 1s)

谁能告诉我是否有这样的警告,如果没有,为什么 gcc 不关心它?

4

2 回答 2

6

AFAIK gcc不提供这样的选项。正如您所引用的,该标准说:

N3690 - §5.8.3

E1 >> E2 的值是 E1 右移 E2 位位置。如果 E1 具有无符号类型或 E1 具有带符号类型和非负值,则结果的值是 E1/2E2 的商的整数部分。如果 E1 具有带符号类型和负值,则结果值是实现定义的。

这意味着做

int si    = -1;
int right = si >> 1;

结果可能会或可能不会产生-1。它是实现定义的。这意味着编译器不会被迫产生类似“其他编译器可能会以另一种方式执行此操作”的警告。

这种选择的一些原因如下。

原始的 K&R 段落说:

“右移无符号数量,用 0 填充空位。右移有符号数量将在某些机器(例如 PDP-11)上填充符号位(算术移位),而在其他机器上填充 0 位(逻辑移位)。”

这意味着操作依赖于架构。这背后的原因是一些架构在做两者之一但不是两者都快速。

这个原因加上符号扩展移位的有用性是微不足道的这一事实使得标准选择将其保留为“实现定义”。通过“符号扩展移位的有用性”,我的意思是在算术上右移一个负有符号整数不能作为正对应物(因为在右边丢失 1 会使负数更小,即模数更大)

+63 >> 1 = +31 (integral part of quotient E1/2E2)
00111111 >> 1 = 00011111
-63 >> 1 = -32 
11000001 >> 1 = 11100000

进一步阅读的参考资料:

https://stackoverflow.com/a/1857965/1938163

http://www.ccsinfo.com/forum/viewtopic.php?t=45711

https://isocpp.org/std/the-standard


编辑:如果上面没有解决问题(即代码是有效的,编译器为什么要警告呢?)我提供了第二种解决方案:AST matchers

如此处所述:http: //eli.thegreenplace.net/2014/07/29/ast-matchers-and-clang-refactoring-tools/您可以编写一些代码来快速识别所有右移-with-signed-程序中的整数点。

将其视为“编写我的小型单任务静态分析检查器”。


编辑 2:您也可以尝试其他静态分析工具,clang 有一个-fsanitize=shift选项可以为您工作。AFAIK 还为gcc实现了一个未定义的行为清理程序,可以帮助诊断这些错误。我没有关注这个故事,但我想你也可以尝试一下。

于 2014-07-31T09:50:23.693 回答
2

GCC 文档在这里

答案似乎是“不”,没有任何你想要的警告选项。

我不知道为什么,但我猜那里有太多的代码使用它,它会太吵。C 可能没有定义算术移位,但现在几乎所有 CPU 都以相同的方式执行,所以大多数人认为它是这样定义的。

于 2014-07-31T10:00:01.933 回答