“int mask = ~0;” 的目的是什么?

44

我在C语言中看到了以下这行代码(链接)

 int mask = ~0;

我已经在C和C++中打印了mask的值,它总是打印出-1

因此我有一些问题:

  • 为什么要给mask变量赋值~0
  • ~0的目的是什么?
  • 我们可以用-1代替~0吗?

39
在二进制的2补码中,数值~0等同于-1 - phuclv
1
@PaulFloyd:链接的源代码是一个纯位操作练习...和举重一样有用。 - 6502
19
在代码中使用有符号类型的掩码告诉我你的代码中可能存在严重问题。 - Sopel
1
可能是什么是~运算符?的重复问题。 - Bernhard Barker
显示剩余2条评论
6个回答

81

这是一种便携式的方法,可以将整数中的所有二进制位设置为1位,而无需知道当前架构中整数的位数。


7
-1会将一个整数中的所有位设置为1,而不需要知道int类型的宽度。这只是暗示使用二进制补码。 - phuclv
26
@LưuVĩnhPhúc 正确。 ~0 方法具有较少的依赖性。适用于无符号、非二进制补码系统,且(可能)更不晦涩。 - Richard Hodges
3
@chqrlie 为什么不呢?顺便说一句,对于位掩码使用有符号值是一个奇怪的想法。 - 0___________
1
@PeterJ_01:如果longint有更多的位数,那么~0u(类型为unsigned)将作为初始化u的一部分被_零扩展_。 - hmakholm left over Monica
5
如果在补码机器上,这个代码不是可移植的:它会生成一个负零,而符合规范的编译器可能会认为这是一个陷阱表示。此时就会产生未定义行为。公平地说,这样的实现可以说在第一步中根本没有针对 int 类型的全 1 值。 - Kevin
显示剩余11条评论

36

C和C++支持三种不同的有符号整数格式:原码、反码和补码

~0会产生所有位都是1的结果,无论系统使用哪种符号格式。因此,它比-1更具可移植性

你可以添加U后缀(例如-1U)来可移植地1生成全为1的位模式。然而,~0更清晰地表明了意图: 反转值0中的所有位,而-1将显示需要一个负一的值,而不是它的二进制表示。

1因为无符号操作总是取模于比该类型所能表示的最大值还要大一的数字


文本表示可以假设为2的补码32位整数。 - 6502
1
请注意,对于所有无符号类型,“u = -1;”将设置所有位为1。对于比“unsigned”更宽的无符号类型,“u = ~0;”和“u = -1u;”则不会这样做。 - chux - Reinstate Monica

8

在使用2的补码平台时(假设),-1是表示为全1,但根据规则,直接写-1是不允许的(只允许使用整数0..255、一元运算符!~和二元运算符&^|+<<>>)。


5
你正在学习一个编程挑战,有一些限制条件要求使用特定的操作符和语言结构完成指定任务。
第一个问题是在不使用减号运算符的情况下返回值-1。
在使用补码表示负数的机器上,值为-1的二进制数所有位均置为1,因此~0将被计算为-1:
/* 
 * minusOne - return a value of -1 
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 2
 *   Rating: 1
 */
int minusOne(void) {
  // ~0 = 111...111 = -1
  return ~0;
}

文件中的其他问题并非总是正确实现。第二个问题是返回表示一个 int 值是否适合于 16 位有符号 short 的布尔值,其中存在一个缺陷:

/* 
 * fitsShort - return 1 if x can be represented as a 
 *   16-bit, two's complement integer.
 *   Examples: fitsShort(33000) = 0, fitsShort(-32768) = 1
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 8
 *   Rating: 1
 */
int fitsShort(int x) {
  /* 
   * after left shift 16 and right shift 16, the left 16 of x is 00000..00 or 111...1111
   * so after shift, if x remains the same, then it means that x can be represent as 16-bit
  */
  return !(((x << 16) >> 16) ^ x); 
}

将一个负值或一个移位后超出 int 范围的数字向左移位具有未定义的行为,右移动负值是实现定义的,因此上述解决方案是不正确的(尽管它可能是预期的解决方案)。


2
很久以前,在像1K ZX 80或ZX 81这样极为有限的设备上,这就是你如何节省内存的方法。在BASIC中,你会:
Let X = NOT PI

相比于
LET X = 0

由于数字被存储为4字节浮点数,后者比第一个NOT PI替代方案多占用了2个字节,其中每个NOT和PI占用一个字节。


并不完全是这样。在这些示例中,所有(关键)单词都由单个字节编码,等号、变量名和“可见”的零本身也是如此。但是,数字常量后面跟着(一个转义字节?)加上4字节的浮点值,这被执行器所利用,但在源代码中不显示。因此,这里的大小差异是3或甚至4个字节,而不是2个字节。 - CiaPan
我应该在工作...只是在ZX Spectrum模拟器上做了这个,并将其保存到磁带/磁盘上。第一个使用40字节,第二个使用35字节,因此差异为5字节。好吧,如果你只有1k像ZX 81一样,我想这是文件大小节省的一半百分比,这就是为什么它被使用的原因...回到OP的问题-今天你能通过使用x = ~0而不是x = -1来节省一个或两个字节(或5个字节)吗?也许使用转换,如long x = ~0? - skaak
我真的应该去工作了……只是尝试了几种简单的C语言方式,但生成的编译文件在-1、0和(0x00)这三个方面的大小是相同的……我确信有些体系结构可以通过这种方式节省一个字节左右,但是当然必须避免这种怪异的现象。生成的文件大约为8.5k字节。这会让可怜的ZX81心脏病发作…… - skaak
1
现代的C编译器足够聪明,能够将~0-1识别为相同的值。它们还可以将代码优化到最大速度或最小尺寸,因此您需要尝试不同的编译选项。此外,编译后的代码可以舍入为4、8或16字节,以更好地对齐函数的入口点,因此查看obj文件大小并不适合您的目的。您可能更愿意将C代码编译为汇编代码,甚至反汇编已编译的对象模块(使用类似objdump的工具)以查看代码的逐字节表示。 - CiaPan
这些系统上没有整数类型吗?PI如何存储在单个字节中? - phuclv
这个特定的系统只有一种数字类型,如果我没记错的话,它将浮点数和整数结合在一起。任何数字,例如1或0或3.141或其他数字都使用4个字节。另一方面,所有关键字(如if、then、else、pi、sin、cos、tan、goto等)都被转换为一个字节。因此,为了节省内存,程序员会做x = not pi而不是x = 0,就像解释的那样。疯狂难以阅读的东西,但是在那些年代和那些系统中,代码也是如此。 - skaak

0

在所有计算机架构中,有多种编码数字的方式。当使用2的补码时,这将始终成立:~0 == -1。另一方面,一些计算机使用1的补码来编码负数,对于这些情况,上述示例不成立,因为~0 == -0。是的,1的补码有负零,这就是为什么它不太直观。

所以回答你的问题:

  • 将~0分配给掩码,使掩码中的所有位都等于1->使mask & sth == sth
  • 使用~0可以使所有位都等于1,而不管所使用的平台
  • 如果您确定您的计算机平台使用2的补码数字编码,则可以使用-1代替~0

我的个人想法-尽可能使您的代码与平台无关。成本相对较小,代码变得无故障。


只有当1s补码实现支持负零时,才能这样做。另请参见此答案 - JHBonarius

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