首页 > 解决方案 > 小类型(char和short)的来回移位行为

问题描述

假设我想将变量的前i位设置c为零。一种方法是左移i位,然后右移相同的量。这是一个执行此操作的简单程序:

#include <iostream>

int main() {
    using type = unsigned int;
    type c, i;
    std::cin >> c >> i;
    c = (c << i) >> i;
    std::cout << c << "\n";
    return 0;
}

但是当类型是unsigned shortor时unsigned char,这不起作用,并且c保持不变。从一方面来看,这是完全可以预料的,因为我们知道寄存器至少有 32 位宽,并且来回移动一两个字节不会将最左边的位设置为零。但问题是:这样的行为如何符合标准和 operator<< 的定义?c = (c << i) >> i;从语言的角度来看,行为不一样的原因是什么c <<= i; c >>= i;?它甚至是定义的行为吗?如果是,是否有其他示例在语义等效的代码之间呈现不同的行为?(或者为什么这两行不等效?)
我还查看了生成的程序集,并且使用 -O2 它看起来更多或对于任何类型,都不像这样:

    sall    %cl, %esi
    shrl    %cl, %esi

但是如果我们让i保持不变,那么 g++ 用2^(n_bits - i) - 1屏蔽整数,但是从不费心为短裤和字符生成任何指令,并在从 cin 获取后立即打印它们。因此,它肯定知道它是如何工作的,因此即使我找不到任何东西,也应该在某处记录这种行为。

PS当然有更可靠的方法将所需的位设置为零,例如gcc在知道i时使用的方法,但这个问题更多的是关于行为规则而不是设置位域。

标签: c++g++

解决方案


这种行为如何符合标准和 operator<<? 的定义?

您观察到的行为符合标准。

它甚至是定义的行为吗

是的,它已定义(假设i不会太大而导致溢出;您将无法使用此方法将所有位设置为零)。

为什么这两行不等价?

因为没有比intC++ 低等级的整数类型的算术运算,并且所有较小类型的算术操作数都被隐式转换为有符号 int的。这种隐式转换称为提升。

有符号右移和无符号右移的行为是不同的。有符号右移扩展最左边的位以使符号保持不变,而无符号右移用零填充最左边的位。

第二个版本的行为不同,因为中间结果具有较小的无符号类型,而第一个版本中的中间结果是提升的有符号类型int(在shortchar小于的系统上int)。


推荐阅读