小类型(char 和 short)来回移动的行为
Behavior of back and forth shift of small types(char and short)
假设我想将变量 c
的前 i 位设置为零。
其中一种方法是左移 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 short
或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 获取后立即打印它们。所以,它明确地知道它是如何工作的,因此应该在某个地方记录这种行为,即使我找不到任何东西。
P.S. 当然还有更可靠的方法将所需位设置为零,例如 gcc 使用 when knows i,但这个问题更多的是关于行为规则而不是设置位域。
how does such behavior comply with the standard and the definition of operator<<?
您观察到的行为符合标准。
Is it even defined behavior
是的,它被定义了(假设i
不会太大而导致溢出;您将无法使用此方法将所有位设置为零)。
why aren't this two lines equivalent?
因为C++中没有比int
低阶的整数类型的算术运算,所有较小类型的算术操作数都隐式转换为signedint
。这种隐式转换称为提升。
有符号右移和无符号右移的行为不同。有符号右移扩展最左边的位,使符号保持不变,而无符号右移用零填充最左边的位。
第二个版本的行为不同,因为中间结果具有较小的无符号类型,而第一个版本中的中间结果是提升的有符号 int
(在 short
和 [=15 的系统上=] 小于 int
).
假设我想将变量 c
的前 i 位设置为零。
其中一种方法是左移 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 short
或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 获取后立即打印它们。所以,它明确地知道它是如何工作的,因此应该在某个地方记录这种行为,即使我找不到任何东西。
P.S. 当然还有更可靠的方法将所需位设置为零,例如 gcc 使用 when knows i,但这个问题更多的是关于行为规则而不是设置位域。
how does such behavior comply with the standard and the definition of operator<<?
您观察到的行为符合标准。
Is it even defined behavior
是的,它被定义了(假设i
不会太大而导致溢出;您将无法使用此方法将所有位设置为零)。
why aren't this two lines equivalent?
因为C++中没有比int
低阶的整数类型的算术运算,所有较小类型的算术操作数都隐式转换为signedint
。这种隐式转换称为提升。
有符号右移和无符号右移的行为不同。有符号右移扩展最左边的位,使符号保持不变,而无符号右移用零填充最左边的位。
第二个版本的行为不同,因为中间结果具有较小的无符号类型,而第一个版本中的中间结果是提升的有符号 int
(在 short
和 [=15 的系统上=] 小于 int
).