什么是位掩码?

286

我对C编程还比较新,最近接触到了位掩码,想了解一下它的一般概念和功能。

提供一些例子将更容易理解。


6
我知道不应该张贴链接,但维基百科的解释非常好:https://en.wikipedia.org/wiki/Mask_(computing) - pevik
6个回答

360

掩码用于定义您想要保留哪些位和您想要清除哪些位。

掩码操作是将掩码应用于值的过程。这可以通过以下方式完成:

  • 按位与(Bitwise ANDing)以从值中提取一部分位
  • 按位或(Bitwise ORing)以设置值中的一部分位
  • 按位异或(Bitwise XORing)以切换值中的一部分位

以下是一个从值中提取一部分位的示例:

Mask:   00001111b
Value:  01010101b

应用掩码到值上意味着我们想要清除前4位(高位),并保留最后4位(低位)。 因此,我们已经提取了低4位。 结果是:

Mask:   00001111b
Value:  01010101b
Result: 00000101b

掩码是使用AND实现的,因此在C中我们得到:

uint8_t stuff(...) {
  uint8_t mask = 0x0f;   // 00001111b
  uint8_t value = 0x55;  // 01010101b
  return mask & value;
}

以下是一个相当常见的用例:从一个较大的字中获取单个字节。我们将字中的高位比特定义为第一个字节。我们使用两个运算符来实现这一点,&>>(向右移位)。以下是如何从32位整数中提取四个字节的方法:

void more_stuff(uint32_t value) {             // Example value: 0x01020304
    uint32_t byte1 = (value >> 24);           // 0x01020304 >> 24 is 0x01 so
                                              // no masking is necessary
    uint32_t byte2 = (value >> 16) & 0xff;    // 0x01020304 >> 16 is 0x0102 so
                                              // we must mask to get 0x02
    uint32_t byte3 = (value >> 8)  & 0xff;    // 0x01020304 >> 8 is 0x010203 so
                                              // we must mask to get 0x03
    uint32_t byte4 = value & 0xff;            // here we only mask, no shifting
                                              // is necessary
    ...
}

请注意,您可以交换上述运算符的顺序,您可以首先进行掩码操作,然后进行移位操作。结果是相同的,但现在您必须使用不同的掩码:

uint32_t byte3 = (value & 0xff00) >> 8;

13
回答很好,但掩码也可以用于使用OR或XOR操作和适当的掩码来设置切换特定位。 - Paul R
“b” 表示二进制字面量,不是所有编译器都支持,对吗? - Ungeheuer
2
一部分的困惑可能是因为选择名词和动词不当来描述实际发生的情况。例如,使用像“Bitselector”或“TargetBits”这样的词似乎更适合描述对象,而不是使用“Bitmask”这个词?同样,为了描述位操作,使用像“BitManipulate”这样的词似乎比使用“Masking”这个词更合适,因为后者只对AND操作有意义,而不适用于XOR(切换位)或OR(设置位),它们是位操作或转换,而不是掩码操作。 - nurabha
那么,掩码是在位运算中使用的字节吗?为什么称其为“掩码”? - Margach Chris
假设我这样使用了一个位掩码:" if (value & 0x0F) "。那么,如果我之后想要使用变量'value',那么这个变量'value'会包含什么值呢?它会包含原始数据还是掩码操作后的更改数据? - Naman Durve
显示剩余4条评论

181

掩蔽是指保留、更改或删除所需信息的一部分。让我们看一个图像掩蔽操作; 就像这个掩蔽操作正在去除不是皮肤的任何东西:

输入图片描述 here

在此示例中,我们执行了一个运算。还有其他掩蔽运算符 - XOR


位掩蔽是指对位进行掩蔽。这里是一个带有运算的位掩蔽 -

     1 1 1 0 1 1 0 1     input
(&)  0 0 1 1 1 1 0 0      mask
------------------------------
     0 0 1 0 1 1 0 0    output

因此,只有中间四个位(因为这些位在此掩码中为1)保留。

让我们使用XOR来看一下——

     1 1 1 0 1 1 0 1     input
(^)  0 0 1 1 1 1 0 0      mask
------------------------------
     1 1 0 1 0 0 0 1    output

现在,中间的四个比特被翻转了(1 变成了 00 变成了 1)。


因此,使用位掩码可以访问单个位 (示例)。有时,这种技术也可用于提高性能。以这个为例-

bool isOdd(int i) {
    return i%2;
}

这个函数用来判断一个整数是奇数还是偶数。我们可以使用位掩码更有效地实现相同的结果 —

bool isOdd(int i) {
    return i&1;
}

简要解释:如果二进制数的最低有效位(LSB)1,则该数为奇数;如果是0,则为偶数。因此,通过与1进行AND操作,我们可以删除除最低有效位以外的所有其他位,即:

     55  ->  0 0 1 1 0 1 1 1     input
(&)   1  ->  0 0 0 0 0 0 0 1      mask
---------------------------------------
      1  <-  0 0 0 0 0 0 0 1    output

3
另外,如果一个整数是偶数,将其转换为奇数:i=i|1。当我们尝试生成像1、3、5、...、2、4、6、...这样的序列时,这非常方便。 - Harshit Sharma
1
您还可以使用以下操作来查找仅具有整数中最低有效位的数字:lsb = i&-i - Harshit Sharma

160

位和字节

在计算机中,数字以二进制形式内部表示。这意味着,当您为变量使用整数类型时,它实际上将被表示为零和一的总和。

正如您所知,一个比特表示一个0或一个1。八个这些位的串联表示一个字节,例如00000101,它是数字5。 我假设您知道数字如何以二进制形式表示,如果不知道,请看看这里

在PHP中,一个数字(大多数情况下)有4个字节长。这意味着您的数字实际上使用了32位的内部存储。但出于简单起见,在本答案中,我将使用8位数字。

使用位存储状态

现在想象一下,你想要创建一个程序来保存状态,该状态基于多个值,这些值是一个(true)或零(false)。可以将这些值存储在不同的变量中,无论是布尔型还是整数型。或者使用单个整数变量,并使用其内部32位的每个位来表示不同的true和false。

一个例子:00000101。这里从右到左读取第一位(reading from right to left)为true,表示第一个变量。第二位为false,表示第二个变量。第三位为true。以此类推...

这是一种非常紧凑的存储数据的方式,有许多用途。

位掩码

这就是位掩码发挥作用的地方。听起来很复杂,但实际上很简单。

位掩码允许您使用在位级别上工作的操作。

  • 编辑字节中的特定位(bit)
  • 检查特定位(bit)值是否存在。

实际上,您将一个掩码(mask)应用到一个值上,在我们的情况下,该值是我们的状态00000101,而掩码是再次是一个二进制数,它指示感兴趣的位(bit)。

通过对掩码和状态进行二进制操作,可以实现以下功能:

  • AND运算符提取状态中的一部分位
  • OR运算符设置状态中的一部分位
  • XOR运算符切换状态中的一部分位

如果我们想将特定值设置为true,则可以使用OR运算符和以下位掩码:

Mask:   10000000b
Value:  00000101b
---- OR ---------
Result: 10000101b

或者可以使用 AND 运算符从状态中选择特定的值:

Mask:   00001100b
Value:  00000101b
---- AND ---------
Result: 00000100b

我建议您深入了解它,并熟悉这个术语。一个很好的开始可能是this链接。
祝您好运!

1
我对你所说的“value”和“mask”的术语持有异议。实际上,掩码才是会覆盖原值的东西。也就是说,你先有一个数值,然后再用掩码进行处理,最终得到结果。(如果你同意的话,请交换一下这两个术语) - Mike Graf

28

这只是一个在二进制中表示的数字。例如,假设我有8个布尔值(truefalse)要存储。我可以将其存储为8个布尔值的数组,或者我可以将其存储为一个单独的字节(8位),每个位都存储其中一个布尔值(0 = false1 = true)。

此时,我可以轻松操纵我的字节,以便我可以(1)设置特定的位开或关(true或false),以及(2)检查特定的位是否开或关。

  • 将位设置为1:mask = mask | (1 << bitIndex)
  • 将位设置为0:mask = mask & ~(1 << bitIndex)
  • 获取位(以便检查它):(mask & (1 << bitIndex)) != 0

所有这些操作都使用左移运算符,它将位从最低有效位置移动到最高有效位置。


18

实质上,Bitmask 是一个布尔标志列表(例如 isAlive、isMoving 等)压缩成一个单一字段,通常是一个整数。它可以显著减少 JSON 字符串大小或内存占用。

这在 PHP 中尤其重要,因为数组中的单个布尔值可能占用与整数相同的 RAM。有一个非常简单的 Bitmask 指南,它将逐步解释您需要了解的所有内容,包括如何以及何时使用它。

编辑:这里是一个存档链接:https://archive.is/QwCUX,因为原始网站似乎已经关闭。


0
我将尝试解释“掩码”在计算机中应用于除了位数组之外的其他数据结构时的含义,并举几个掩码可能适用的例子。位掩码之所以能够起作用,是因为它们被定义为固定大小或具有预设的0值,因此它们执行AND/OR掩码(请参见@DJanssens的出色答案)。
首先,为了说明这一点,我们来打个比方:从你的脸部照片开始。当你照镜子时(正对着),你看到的是你的脸——你的脸颊、额头、下巴、眼睛等等。
现在你戴上面具(如果你不知道面具可能长什么样子,请搜索“威尼斯半面具”的图片)。
你在镜子里看到了什么?你看到的是面具覆盖住的地方。你的皮肤或下巴或其他未被覆盖的部分不会显示出来。面具遮盖了它所覆盖的内容,但呈现出与基础相同的大小。 好的,现在我们进入计算机领域,我们也可以对许多数据类型进行掩码操作。例如对象、数组、树等等。它们可以通过加法进行操作(添加掩码中缺失的值,或仅在键的交集上进行操作,这意味着如果一个键不在基础中,则忽略任何掩码)。
例如1 对象
Base = 
{
  "A": "baseA",
  "B": "baseB",
}
Mask = 
{
  "A": "maskA",
  "C": "maskC",
}
==> 
{
 "A": "maskA", // Masked / overridden by the mask
 "B": "baseB", // Pass through from base because not masked
 "C": "maskC", // Added because present in the mask (null/undefined in base)
}

例2 数组 假设null是一个哨兵值,而不是有效值

base = ["base1", "base2"]
mask = [null, "mask2", "mask3"]
==> ["base1", "mask2", "mask3"]

例3 树 假设null是一个金丝雀值,而不是有效值

Base = 
  B
 /
A
 \ 
  C
Mask =
        E
       /
      D
     /
null 

Output = 
    E // Added by presence in mask
   /
  D // Override base value because present in mask
 /
A // Passthrough from base because `null` in mask means pass through
 \
  C // pass through because undefined in mask

EG 4 字符串

我的同事告诉我,在计算机中“mask”一词还有另一个关键用途,可应用于字符串,常用于日志。该掩码通常不会被显式地定义为输入值,而更像是一种函数,可以屏蔽敏感数据。

base = "[INFO] user logged in with user=stackoverflow@example.com password=123456"

mask(base) ==> "[INFO] user logged in with user=stackoverflow@example.com password=******" 

(有时不匹配掩码长度以隐藏所掩盖的项目的长度)

请查看:https://github.com/gwpmad/mask-deep,这是使用“mask”一词的示例。


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