【问题标题】:Bitshift - Need explanation to understand the codeBitshift - 需要解释才能理解代码
【发布时间】:2014-08-20 19:30:46
【问题描述】:

我想知道这个函数实际上执行了什么。 据我了解,它应该返回 pSrc[1]。

那么为什么它会将 pSrc[0] 左移 8 位,这会将这 8 位清零。 当这些零与 pSrc[1] 进行 OR 运算时,pSrc[1] 不会受到影响,因此无论如何您都会得到 pSrc[1],就好像按位 OR 从未发生过一样。

/*
* Get 2 big-endian bytes.
*/
INLINE u2 get2BE(unsigned char const* pSrc)
{
    return (pSrc[0] << 8) | pSrc[1];
}

这个函数来自dalvik虚拟机的源码。 https://android.googlesource.com/platform/dalvik/+/android-4.4.4_r1/vm/Bits.h

更新:

好的,现在我明白了,这要感谢这里的所有答案。

(1) pSrc[0] 原本是一个无符号字符(1 字节)。

(2) 当它用 int 类型的字面量 8 左移 (pSrc[0]

(3) pSrc[0]

(4) 当它被 ORed 时(步骤 (3) | pSrc[1] 的中间结果),pSrc[1] 然后被 int-promoted 为有符号的 int(4 个字节)。

(5) ( 步骤 (3) 的中间结果 | pSrc[1]) 的结果将前两个最低有效字节保留为我们想要的方式,其中两个最高有效字节全部为零。

(6) 通过将结果返回为 u2 类型,仅返回前两个最低有效字节以获得 2 个大端字节。

【问题讨论】:

标签: c++ c bit-manipulation bit-shift


【解决方案1】:

对于这样的算术运算,unsigned char 通过一个称为积分提升的过程进行转换。

C++11 - N3485 §5.8 [expr.shift]/1:

操作数应为整数或非范围枚举类型,并执行整数提升。结果的类型是提升的左操作数的类型。

以及§13.6 [over.built]/17:

对于每对提升的整数类型 L 和 R,存在形式为

的候选运算符函数
LR operator%(L , R );
LR operator&(L , R );
LR operator^(L , R );
LR operator|(L , R );
L operator<<(L , R );
L operator>>(L , R );

其中 LR 是类型 L 和 R 之间通常的算术转换的结果。

当积分促销完成时(§4.5 [conv.prom]/1):

除 bool、char16_t、char32_t 或 wchar_t 之外的整数类型的纯右值,其整数转换 rank (4.13) 小于 int 的 rank 可以转换为 int 类型的纯右值,如果 int 可以表示所有 源类型的值;否则,源纯右值可以转换为无符号类型的纯右值 诠释。

通过积分升级,unsigned char 将升级为int。另一个操作数已经是int,因此不会对其进行类型更改。然后返回类型也变为int

因此,您所拥有的是第一个unsigned char 的位向左移动,但仍在现在更大的int 中,然后是第二个unsigned char 的位在末尾。

您会注意到operator| 的返回类型是两个操作数之间通常的算术转换的结果。此时,这些是来自班次的int 和第二个unsigned char

这种转换定义如下(§5 [expr]/10):

许多期望算术或枚举类型操作数的二元运算符会导致转换和产生 结果类型以类似的方式。目的是产生一个通用类型,这也是结果的类型。 这种模式称为通常的算术转换,定义如下:

否则,应在两个操作数上执行积分提升 (4.5)。那么以下 规则应应用于提升的操作数:

如果两个操作数的类型相同,则无需进一步转换。

由于在此之前被提升的LR 已经是int,因此提升使它们保持不变,因此表达式的整体返回类型为int,然后转换为@987654337 @,不管发生了什么。

【讨论】:

  • 不正确。通常的算术转换适用于移位运算符。
  • @JamesKanze,那么,这很有趣。 §13.6 [over.built]/17 似乎另有建议。等等,刮一下。我误读了。稍后返回。
  • &lt;&lt; 的定义在 §5.8 中,并且没有提到“通常的算术转换”。 §13.6 也没有提到关于移位运算符的“通常的算术转换”:它说前面列表中的 LR 是类型 L 和 R,但它不使用 LR 是对&lt;&lt;&gt;&gt; 的重载的描述。
  • 你仍然得到“因为 shift 的右手参数是 int”。移位的右手参数对被移位的类型和结果类型都没有影响。
  • @JamesKanze,好的,谢谢。从 UAC 那里仍然存在。
【解决方案2】:

没有任何操作(除了类型转换) unsigned char。任何操作前,积分提升 发生,这会将unsigned char 转换为int。所以 操作是将int 向左移动,而不是unsigned char

【讨论】:

    【解决方案3】:

    C11 6.5.7 移位运算符

    对每个操作数执行整数提升。方式 的结果是提升的左操作数的结果。 如果值 右操作数为负数或大于或等于 提升的左操作数的宽度,行为未定义。

    E1 如果 E1 有一个签名 类型和非负值,并且 E1 × 2E2 在结果类型中是可表示的,那么就是 结果值;否则,行为未定义。

    所以pSrc[0] 是整数提升为int。文字 8 已经是 int,因此不会发生整数提升。通常的算术转换不适用于移位运算符:它们是一种特殊情况。

    由于原始变量是一个左移 8 位的unsigned char,我们还遇到了“E1”(我们的提升变量)被签名的问题,并且结果可能无法在结果类型中表示,这导致如果这是 16 位系统,则行为未定义。

    简单地说:如果您将某些内容移入有符号变量的符号位,则任何事情都可能发生。一般来说:依赖隐式类型提升是不好的编程和危险的做法。

    您应该将代码修复为:

    ((unsigned int)pSrc[0] << 8) | (unsigned int)pSrc[1]
    

    【讨论】:

    • 关于未定义行为的评论是一个很好的观点;此代码在具有 16 位 ints 的机器上被破坏,因为提升导致签名类型。
    猜你喜欢
    • 1970-01-01
    • 2016-09-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多