静态断言有符号右移具有二进制补码行为是否合法?

3
在C11、C++11和C++14中,以下操作是否合法?
static_assert(((-4) >> 1) == -2, "my code assumes sign-extending right shift");

或者使用C语言的等效方式:
_Static_assert(((-4) >> 1) == -2, "my code assumes sign-extending right shift");

我不知道关于常量表达式的规则,是否可以使用像上面那样的实现定义操作。

我知道相反的、对负数进行有符号左移的操作是未定义的,无论机器类型如何。


考虑在您的代码中使用 / 来完全避免此问题。无论移位定义如何,(-4) / 2 始终为 -2 - M.M
@Matt: 虽然它具有向零舍入,但移位硬件通常会进行向下舍入。因此它很容易变得非常缓慢,也许向下舍入是所需的。 - Ben Voigt
@MattMcNabb:我滥用有符号的 >> 的原因是为了在高位构建掩码。例如,x >> 31 相当于 x < 0 ? -1 : 0。当然,编译器已经为您执行了这种优化,但有时它并不知道自己在做什么。此外,这是在我的代码中极其繁忙的部分完成的。 - Myria
@Myria 试着检查一下你的编译器在最大优化级别下生成的汇编代码,看看这是否真的是一个问题。在我的系统上,使用 gcc -O3,两者都会执行 sarl $31, %eax。最大可移植性和最大速度通常是相互冲突的目标。 - M.M
说实话,编写一个宏来执行算术运算、二进制补码右移操作是微不足道的,即使编译器不支持,在任何优化良好的编译器上,都可以将其折叠成对于自然具有所需行为的目标而言只是普通移位操作。 - R.. GitHub STOP HELPING ICE
只要包含了<assert.h>,C语言中的等效写法仍然是static_assert(... - Cubbi
2个回答

6
是的。C++11标准在[expr.shift]/3中说:
E1 >> E2 的值是将 E1 向右移动 E2 位得到的结果。 如果 E1 具有无符号类型,或者如果 E1 具有有符号类型并且是非负值,则结果的值为E1/2^E2的商的整数部分。 如果 E1 具有带符号类型和负值,则结果的值是实现定义的。
而在[expr.const]/2中没有说这样的移位或具有实现定义值的表达式不是常量表达式。 因此,您将获得一个具有实现定义值的常量表达式。

3

只要不引起未定义行为,这是合法的。

对于负值的右移操作的行为是实现定义的。C和C++标准不保证其是算术还是逻辑;尽管据我所知,从来没有一款CPU不选择其中之一。


2
只有当值为负数时,它才是实现定义的。 - chris
这就是关键,你应该有不止一个测试来确保你得到了所需的行为。 - Ben Voigt
@chris:在这个问题中,确实如此。 - Ben Voigt
1
@BenVoigt,只是回答的措辞让人觉得它是每个输入的ID。 - chris
我猜加上AND(最后一位移出的符号位)不是不合理的,这会导致向零舍入而不是向负无穷大舍入。 - Ben Voigt
@BenVoigt 我不确定添加更多的测试是否有太大的用处。技术上,实现定义允许像编译器早上进行算术移位,晚上进行逻辑移位这样的事情,尽管在实际情况下,人们必须假设编译器将记录一些明智的东西,否则没有人会使用该编译器。 - M.M

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接