为什么我的C语言位运算构造不起作用

3

我想做的是制作一个掩码,该掩码从一系列位的最左侧具有1比特,其余为零,无论变量大小如何。我尝试了以下方法:

unsigned char x = ~(~0 >> 1);

我认为这应该适用于char或int类型,但事实并非如此!

对我来说,操作看起来像这样:

||||||||
0|||||||
|0000000

这是看起来应该的样子,并且使用16位整数:

|||||||| ||||||||
0||||||| ||||||||
|0000000 00000000

为什么这个结构体不起作用?无论我尝试将它分配给无符号字符还是整数,都会得到零值。
我现在才看了50页的K&R,所以我还是新手。我不知道什么是字面量,也不确定什么是“算术”移位,我不知道如何使用后缀,更不用说使用结构体了。

11
你知道你正在移动的这些字面量是整数吗? - indiv
3
需要在移位之前将类型转换为无符号类型。有符号类型使用算术右移,它会复制符号位(最高位)。使用所需大小的无符号类型。使用~(~(unsigned_type)0 >> 1) - programmerjake
5
1<<(8*sizeof(x)-1) 不是更加直观吗? - Jongware
3
“有符号类型使用算术右移”- 实际上并不是这样;它的具体实现是由编译器定义的。 - Oliver Charlesworth
4
更好的方式是使用1<<(CHAR_BIT*sizeof(x)-1),以处理不使用8位比特的系统。 - Adam Rosenfield
显示剩余8条评论
3个回答

1

~0是所有位都反转的int零,这是由所有1组成的int。在2s补码机器上,这是一个-1。对-1进行右移将导致符号扩展,因此~0 >> 1仍然全部为1。

您需要右移一个unsigned数量,这将不会引起符号扩展。

~0u >> 1

是一个无符号整数,最高位为0,其他所有位都设置为1,因此

~(0u >> 1)

“是一个无符号整数,最高位为1,其他位均为零。”
“要使其适用于所有数据大小并非易事,因为C会在整数运算之前将操作数转换为'int'或'unsigned int'。例如,”
~(unsigned char)0 >> 1

这段代码会产生一个int类型的结果-1,因为在应用~运算符之前,无符号字符被“提升”为int

因此,为了在所有数据类型中获得想要的结果,我唯一能想到的方法是使用sizeof来查看数据中有多少个字节(或八位组)。

#include <stdio.h>
#include <limits.h>
#define LEADING_ONE(X)  (1 << (CHAR_BIT * sizeof(X) - 1))
int main(void) {
  printf("%x\n", LEADING_ONE(char));
  printf("%x\n", LEADING_ONE(int));
  return 0;
}

2
如果你想要严谨一些,你应该使用 CHAR_BIT 而不是 8 - IronMensan
右移 -1 的结果是实现定义的。它可能会导致符号扩展,但不要指望它一定会这样。 - Nisse Engström
我不太理解这个结构:LEADING_ONE(X) (1 << (8 * sizeof(X) - 1))看起来你正在使用指针来查找x的大小,但我对指针的工作原理或如何使用它们并不太熟悉。 - Spellbinder2050
另外,我不理解这些:LEADING_ONE(char) LEADING_ONE(int) - Spellbinder2050
@Spellbinder2050:这里没有指针参与。你将类型名称(int)传递给宏,它应用 sizeof 运算符来计算类型中的字节数。然后它乘以8来获取类型中的位数(假设每个字节有8位)。接下来,一个 signed int 类型的 1 被左移了“位数减1”次,以将其移动到该类型的最高有效位置。这个宏实际上有更多错误,但我在这个评论中没有足够的空间来讲述。 - Nisse Engström

0

C 的一般规则是表达式在一个公共类型中进行评估,这种情况下为(有符号)整数。(~0) 和 (~0 >> 1) 的评估是有符号整数,移位是算术移位。在您的情况下,它是通过符号扩展实现的,因此:

(0xffffffff >> 1) => (0xffffffff)

一个逻辑移位将在左侧注入您期望的零,因此您的问题是如何让编译器执行逻辑移位。尝试:
unsigned char a = ~0;
unsigned char b = a >> 1;  // this should do a logical shift
unsigned char c = ~b;

有更好的方法来解决你正在尝试的问题,但这应该可以帮助你克服当前的问题。


将负值右移的结果是实现定义的。它可能会导致符号扩展,但不要指望它一定会这样。 - Nisse Engström

0

有两件事情导致了您得到了意外的结果。

  1. 您从 0 开始,这被视为一个 signed int
  2. 中间结果被转换为 int

如果您在关键点使用 unsigned char,那么应该没问题。

unsigned char c = ((unsigned char)~0 >> 1);
c = ~c;

我曾认为对于变量 = 某个表达式,操作数在执行操作之前会被转换为赋值符号左侧的内容。现在我假设我的想法是不准确的? - Spellbinder2050
1
@Spellbinder2050,那不是准确的。 = 右侧首先被评估,与 LHS 无关。 如果 RHS 的值类型与 LHS 的类型不同,则在进行赋值之前将 RHS 转换为 LHS 的类型。 - R Sahu
1
@RSahu:没有理由假设右手边会先被评估。在这样的语句中,例如 a[i++] = a[i]LHS 可能会被 首先 评估,这可能会对初学者造成一些惊喜。评估顺序与 RHS 的 类型 在赋值之前转换为 LHS 的 类型 无关。 - Nisse Engström
2
微小的挑剔:仅使用“unsigned char”是不可能的。您答案中的右移操作仍将使用“signed int”(或在奇怪的实现中使用“unsigned int”)。尽管如此,结果应该是预期的(我想)。 - Nisse Engström

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