什么是位掩码问题的好解决方案?

3

问题

  • 我想改变一些位的组合,而不影响其他位。
  • 在这种组合中,一些位必须被取消设置设置

问题:什么是最好的掩码解决方案。特别是对于取消设置掩码。

例如:将bits[3:0] -> 0b1011 (data) 更改为 bits[3:0] -> 0b0110

让我分享一下我尝试过的方法:

  • 创建了一个取消设置掩码:0b1001的取反 --> [1111]0110(将不关心的位更改为1)
  • 创建了一个掩码:0b0110

有了这两个掩码,就可以使用按位操作了。

int data = 0xB;         /*0b1011 (0xb) -> 0110 (0x6)*/
int umask = 0x9;        /*unset mask 0b1001*/
int smask = 0x6;       /*Set Mask 0b0110*/

data &= ~umask;        /*Unset the bits what needs without affecting other*/
data |= smask;         /*Set the bits what needs to be set*/

1
你是在寻求一个通用的解决方案吗?还是想知道除了你提出的那个解决方案之外,是否存在其他(更好的)解决方案来处理这些固定值? - AntonH
1
你所描述的正是通常的做法。你的编译器会处理剩下的部分。 - Lee Daniel Crocker
1
在微控制器(例如AVRs)上进行位操作时,这是非常常见的模式。像 PORTB |= 1 << 2; 这样的一行代码将被转换为单个 SBI 设置位的汇编指令。 - Nick T
2
定义最佳。只要你不担心原子性,上述方法就非常有效。如果你担心原子性,那么需要采取一些额外的步骤,但是掩码代码基本保持不变。此外,你的编译器应该能够从你的代码中找出是否有更好的汇编操作码来完成你想要的操作。 - Michael Dorgan
1
如果您需要将位同时更改为关闭和打开状态,则需要两个掩码。一个用于清除,另一个用于设置。 - Michael Dorgan
显示剩余2条评论
1个回答

2
简单的回答是,是的,您可以使用1个掩码来清除您想要清除的位,并使用另一个掩码来设置您想要设置的位。但是,有时,根据使用掩码和数据的方式以及它们所代表的内容,您需要注意隐藏的硬件级别的问题。
如果您只想将4位设置为一个值:
val &= ~(0xf); // This only clears the low 4 bits, preserving the rest.

assert((new4BitMaskvalue & ~0xf) == 0); // Make sure new mask only plays with low 4 bits.
val |= new4BitMaskvalue;  // or new mask

你在技术上清除一些额外的位,但这样可以让你只使用一个通用的掩码进行清除。如果你像目前这样清除确切的位,可能并不是必要的。通常情况下,如果你将所有字段一次性设置在CPU支持的内存单元大小中(u8/u16/u32/u64等),你可以直接赋予整个值并节省一步。现在,有趣的部分来了。如果这些位代表实际的硬件寄存器,你可能无法像这样设置整个字段,因为它可能会导致令人兴奋的行为。也许这是一个中断使能寄存器。如果是这样,清除和重置不需要清除和设置的位会导致意外的副作用。此外,寄存器通常是只读或只写的,再次需要注意如何屏蔽,并且甚至可能被迫一个接一个地设置或清除各个位。简而言之,是的,通常至少需要两个值来清除和设置位。第一个“not and”掩码通常可以用于清除你所关心的所有位,以便你不需要2个特殊的掩码。

是的,我的主要问题是关于寄存器位操作,但首先我想了解这个问题,然后再尝试。 - Peter
你可能已经用更少的话说了这个重要的事情。如果你正在使用指针技巧,比如你有一个指向硬件寄存器myreg的指针,当你使用它时实际上会触摸到寄存器myreg &= ~0x7; myreg |= 0x2; 你已经改变了那些位,然后又改变了它们,你需要做一个副本temp = myreg; temp &= ~0x7; temp |= 0x2; myreg = temp。所以要理解访问方法。如果这只是一个变量中的数据,那么就像所述的那样,你需要用and来清零位,并用or来设置位。 - old_timer

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