标准的(跨平台)位操作方式

12

由于数字有不同的二进制表示方式(例如,采用大/小端),这是否跨平台:

// NOTE: FIXED-SIZE unsigned integral type
some_unsigned_type variable = some_number;

// set n-th bit, starting from 1,
// right-to-left (least significant-to most significant)
variable |= ( 1 << ( n - 1 ) );

// clear the same bit:    
variable &= ~( 1 << ( n - 1 ) );
换句话说,编译器是否总是处理固定大小的无符号数的不同二进制表示,还是与平台有关?如果变量是带符号整数类型(例如int),且其值为0、正数或负数,那么标准对此有何规定?请注意,我对C和C++都感兴趣,因为我知道它们是不同的语言。如果需要,我可以提供真实示例,但这会使帖子变得太长。

使用哪个版本的C语言?如果您的目标是C99,那么您可以访问 stdint.h,它为整数类型提供了许多跨平台选项。 - Richard J. Ross III
尽管你对C++和C都感兴趣,但并不是我们所有人都精通这两种语言,因此不能确保提供的答案适用于两者。 - Sebastian Mach
3个回答

7

除非some_unsigned_type是一个固定宽度类型,否则这是您首个平台特定的问题。在某些平台上,通过值本身进行移位可能会丢失一些不可重复的信息,而在另一个平台上则不会。例如:

16 bit 'int':

      1000 0000  0000 0000
<<1 = 0000 0000  0000 0000
>>1 = 0000 0000  0000 0000

32 bit 'int':

      0000 0000  0000 0000   1000 0000  0000 0000
<<1 = 0000 0000  0000 0001   0000 0000  0000 0000
>>1 = 0000 0000  0000 0000   1000 0000  0000 0000

C++标准中的“5.8 Shift Operators”也说到了这一点:
行为未定义,如果右操作数为负数或大于或等于左操作数提升后的位数长度。
因此,如果您将整数向左或向右移动超过其位数,则会出现未定义的行为。例如,如果您将一个short类型的值左移17位,则在某些机器上可能会产生UB,但不是所有机器都会这样。
C11在“6.5.7位移运算符”中说到了这个问题,除其他事项外,还有这个:
E1 >> E2的结果是E1向右移动E2个比特位置。如果E1具有无符号类型或者E1具有有符号类型且非负值,则结果的值是E1/2^E2的商的整数部分。如果E1具有有符号类型和负值,则结果的值是实现定义的。
因此,移位有符号数是不可移植的。
因此,对于一般情况下的整数,答案是:
整数的位操作是不可移植的。

1
此国际标准允许对整数类型使用2的补码、1的补码和带符号幅值表示法,但不考虑移位。有符号整数不具备可移植性。同样,字节序也会影响整数的可移植性。 - Mooing Duck
@MooingDuck:不错的观点,尽管这需要一些额外的支持,即“使用位运算符可以操作物理位”。当然,语言可以通过将实际内容翻译成可见内容来使位操作具有可移植性。我旨在通过一些实际的东西来回避这个问题 :) - Sebastian Mach
没错,字节序对于位运算来说并不重要。但是反码和补码的区别会不会让使用 & 时感到困惑呢?如果我取一个值为 -1 的数,并将最高位变成零,那么反码和补码会得到不同的结果,对吗? - Mooing Duck
@Mooing Duck:我的意思是,即使机器在底层使用另一个补码,根据语言标准,它仍然可能需要将位转换为一些一致的东西;我知道这听起来很奇怪,但从快速思考的角度来看,这并非不可能。 - Sebastian Mach
我刚刚查看了规范,除了整数提升之外,我没有看到其他的东西。(不是说它不存在,只是我没有看到) - Mooing Duck
显示剩余2条评论

5
免责声明:我默认你谈论的是具有固定宽度的整数类型。否则,位移操作非常危险... 标准: n3337 C++11
对于无符号类型或有符号类型的正值(*),移位定义是数学的,因此不受基础硬件表示的影响。
5.8 移位运算符 [expr.shift] 2 E1 << E2 的值是将 E1 左移 E2 位,空出的位用零填充。如果 E1 具有无符号类型,则结果的值为 E1 × 2E2,对结果类型可表示的最大值加1取模后减少。否则,如果 E1 具有带符号类型和非负值,并且 E1×2E2 可以在结果类型中表示,则结果值为该值;否则,行为未定义。 3 E1 >> E2 的值是将 E1 右移 E2 位。如果 E1 具有无符号类型或 E1 具有带符号类型并且具有非负值,则结果的值是 E1/2E2 的商的整数部分。如果 E1 具有带符号类型且具有负值,则得到的值是实现定义的。
同样的道理,我认为位运算and、or和negate也是可以的:它们被数学定义了。
5.3.1 一元运算符 [expr.unary.op] 10 ~ 的操作数应为积分或范围未定的枚举类型,其结果是操作数的一补数。 5.11 位与运算符 [expr.bit.and] 1 执行通常的算术转换;结果是操作数的按位AND函数。该运算符仅适用于整数或范围未定的枚举操作数。 5.13 按位包含OR运算符[expr.or] 1 执行通常的算术转换;结果是其操作数的按位包含OR函数。该运算符仅适用于整数或范围未定的枚举操作数。
然而,对于后两个,我承认我不太确定,我找不到“按位XX函数”的定义,因此即使我相信它们是指它们的数学对应部分,我也不能保证。
(*)感谢phresnel指出这一点。

1
"[...] shifts的定义不受硬件表示方式的影响。" vs. "如果E1具有带符号类型和负值,则结果值是实现定义的。" - Sebastian Mach
是的,我确实在谈论固定大小的整数类型。我没有提到这一点,我的错。关于 * -“无符号类型或有符号类型中的正值”- 那么零呢? 正数不等于非负数,对吧? - Kiril Kirov
@KirilKirov:实际上,0应该没问题。例如,在移位时,有一个带符号整数的最左边的位存在问题:在2补码中,它表示符号(0为正,1为负),因此对于负值,通过移位实际上会改变符号...哎呀! - Matthieu M.
没错,你说得对,我知道负数在移位时是这样处理的 :) 我关于 0 的问题有点反问的意味,如果你喜欢的话,你可以更新一下你的答案。你能告诉我一些关于引号的事情吗?它们来自哪个标准,确切地说?你知道它们对所有标准是否都相同吗? - Kiril Kirov
@KirilKirov:我编辑了答案中的标准版本,你说得对,这非常重要。我的理解是0通常被认为是正数,所以我不觉得有必要明确它。 - Matthieu M.
我认为“具有带符号类型和非负值”也适用于 0 - Kiril Kirov

1

如果您移位负数(或有符号数),则具体实现取决于实现方式(尽管大多数实现都是相同的U2)。如果您通过不超过变量位数的值移位无符号数,则对于大多数用途而言,它是可移植的。


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