7

我正在尝试做一种左移,它会在开头添加零而不是一。例如,如果我离开 shift 0xff,我会得到:

0xff << 3 = 11111000

但是,如果我右移它,我会得到:

0xff >> 3 = 11111111

我可以使用任何操作来获得相当于左移的操作吗?即我想得到这个:

00011111

有什么建议吗?

编辑

为了回答评论,这是我正在使用的代码:

int number = ~0;
number = number << 4;   
std::cout << std::hex << number << std::endl;

number = ~0;
number = number >> 4;
std::cout << std::hex << number << std::endl;

输出:

fffffff0
ffffffff

由于它似乎通常应该起作用,我对为什么这个特定代码不起作用感兴趣。任何的想法?

4

6 回答 6

10

这就是 C 和二进制算术的工作原理:

如果你离开 shift 0xff << 3,你会得到二进制:00000000 11111111 << 3 = 00000111 11111000

如果你右移0xff >> 3,你会得到二进制:00000000 11111111 >> 3 = 00000000 00011111

0xff是一个(有符号的)整数,具有正值255。由于它是正数,因此转换它的结果是 C 和 C++ 中定义明确的行为。它不会做任何算术移位,也不会做任何种类或定义不明确的行为。

#include <stdio.h>

int main()
{

  printf("%.4X %d\n", 0xff << 3, 0xff << 3);
  printf("%.4X %d\n", 0xff >> 3, 0xff >> 3);

}

输出:

07F8 2040
001F 31

所以你在你的程序中做了一些奇怪的事情,因为它没有按预期工作。也许您正在使用 char 变量或 C++ 字符文字。


来源:ISO 9899:2011 6.5.7。


问题更新后编辑

int number = ~0;假设二进制补码,给你一个等于 -1 的负数。

number = number << 4;调用未定义的行为,因为您左移了一个负数。该程序正确地实现了未定义的行为,因为它要么做某事,要么什么都不做。它可能会打印 fffffff0 或者可能会打印粉红色的大象,或者它可能会格式化硬盘驱动器。

number = number >> 4;调用实现定义的行为。在您的情况下,您的编译器会保留符号位。这称为算术移位,算术右移以这样一种方式工作,即 MSB 用移位前的任何位值填充。因此,如果您有一个负数,您将体验到该程序正在“移入”。

在现实世界中 99% 的情况下,对有符号数使用位运算符是没有意义的。因此,请始终确保您使用的是无符号数,并且 C/C++ 中没有任何危险的隐式转换规则将它们转换为有符号数(有关危险转换的更多信息,请参阅“整数提升规则”和“通常的算术转换”,关于 SO 的大量好信息)。

编辑 2,来自 C99 标准的基本原理文档 V5.10 的一些信息:

6.5.7 移位运算符

K&R 中移位运算符的描述表明,移位长计数应该迫使左操作数在移位之前扩大到长。C89 委员会认可的一种更直观的做法是,移位计数的类型与结果的类型无关。

C89 中的安静变化

移位长计数不再将移位的操作数强制为长。C89 委员会肯定了 K&R 授予的实现自由,不需要符号右移操作来符号扩展,因为这样的要求可能会减慢快速代码,并且符号扩展移位的有用性是微不足道的。(将负的二​​进制补码整数算术右移一位与除以二不同!)

于 2013-01-18T10:52:03.103 回答
8

如果您明确地转移 0xff 它会按预期工作

cout << (0xff >> 3) << endl; // 31

仅当0xff是有符号宽度为 8 的类型(char并且signed char在流行的平台上)时才可能。


因此,在常见情况下:

您需要使用无符号整数

(unsigned type)0xff

右移用作除以 2(如果我理解正确,则向下舍入)。

所以当你有 1 作为第一位时,你有负值,在除法之后它又是负数

于 2013-01-18T10:27:15.947 回答
5

您正在谈论的两种右移称为Logical ShiftArithmetic Shift。C 和 C++ 对无符号整数使用逻辑移位,大多数编译器将对有符号整数使用算术移位,但这并不能由标准保证,即右移负符号整数的值是实现定义的。

由于您想要一个逻辑移位,您需要切换到使用无符号整数。您可以通过将常量替换为0xffU.

于 2013-01-18T10:36:39.813 回答
3

要解释您的真实代码,您只需要 Lundin 在评论中给出的 C 标准中引用的 C++ 版本:

int number = ~0;
number = number << 4;

未定义的行为。[expr.shift] 说

E1 << E2 的值是 E1 左移 E2 位位置;空出的位是零填充的。如果 E1 具有无符号类型,则结果的值为 E1 × 2 E2,比结果类型中可表示的最大值多模一减少。否则,如果 E1 具有带符号类型和非负值,并且 E1×2 E2在结果类型中是可表示的,那么这就是结果值;否则,行为未定义

number = ~0;
number = number >> 4;

实现定义的结果,在这种情况下,您的实现为您提供了算术移位:

E1 >> E2 的值是 E1 右移 E2 位位置。如果 E1 具有无符号类型或 E1 具有带符号类型和非负值,则结果的值是 E1/2 E2的商的整数部分。如果 E1 具有带符号类型和负值,则结果值是实现定义的

您应该使用无符号类型:

unsigned int number = -1;
number = number >> 4;
std::cout << std::hex << number << std::endl;

输出:

0x0fffffff
于 2013-01-18T12:58:47.350 回答
1

在这里加上我的 5 美分……我面临与 this.lau 完全相同的问题!我对此做了一些敷衍的研究,这些是我的结果:

typedef unsigned int Uint;
#define U31 0x7FFFFFFF
#define U32 0xFFFFFFFF

printf ("U31 right shifted: 0x%08x\n", (U31 >> 30));
printf ("U32 right shifted: 0x%08x\n", (U32 >> 30));

Output:
U31 right shifted: 0x00000001 (expected)
U32 right shifted: 0xffffffff (not expected)

看起来(在没有任何人有详细知识的情况下)XCode for Mac OS X v5.0.1 中的 C 编译器将 MSB 保留为随着每次移位而被拉出的进位位。

有点烦人的是,反之亦然:-

#define ST00 0x00000001
#define ST01 0x00000002

printf ("ST00 left shifted: 0x%08x\n", (ST00 << 30));
printf ("ST01 left shifted: 0x%08x\n", (ST01 << 30));

Output:
ST00 left shifted: 0x40000000
ST01 left shifted: 0x80000000

我完全同意上面的人断言操作数的符号与移位运算符的行为无关。

任何人都可以阐明 C 的 Posix4 实现的规范吗?我觉得一个明确的答案可能就在那里。

与此同时,似乎唯一的解决方法是按照以下思路构建;-

#define CARD2UNIVERSE(c) (((c) == 32) ? 0xFFFFFFFF : (U31 >> (31 - (c))))

这行得通 - 令人恼火但必要。

于 2015-06-19T11:04:27.800 回答
0

以防万一如果您希望在右移后负数的第一位为 0,我们可以做的是将该负数与 INT_MIN 进行 XOR,这将使其 msb 为零,我知道它不合适的算术移位但会完成工作

于 2021-04-14T13:46:41.123 回答