如何设置、清除和切换单个位?

问:

如何设置、清除和切换一点?

答1:

huntsbot.com汇聚了国内外优秀的初创产品创意,可按收入、分类等筛选,希望这些产品与实践经验能给您带来灵感。

设置一点

使用按位或运算符 (|) 设置位。

number |= 1UL << n;

这将设置 number 的第 n 位。如果您想设置第 1 位,n 应该为零,如果您想设置第 n 位,以此类推直到 n-1。

如果 number 比 unsigned long 宽,则使用 1ULL; 1UL << n 的提升直到在评估 1UL << n 之后才会发生,其中移动超过 long 的宽度是未定义的行为。这同样适用于所有其余示例。

清除一点

使用按位 AND 运算符 (&) 清除位。

number &= ~(1UL << n);

这将清除 number 的第 n 位。您必须使用按位非运算符 (~) 反转位字符串,然后将其与。

切换一下

XOR 运算符 (^) 可用于切换位。

number ^= 1UL << n;

这将切换 number 的第 n 位。

检查了一下

你没有要求这个,但我不妨添加它。

要检查一下,请将数字 n 向右移动,然后按位与它:

bit = (number >> n) & 1U;

这会将 number 的第 n 位的值放入变量 bit。

将第 n 位更改为 x

将第 n 位设置为 1 或 0 可以通过以下在 2 的补码 C++ 实现中实现:

number ^= (-x ^ number) & (1UL << n);

如果 x 是 1 位 n 将被设置,如果 x 是 0 则清零。如果 x 有其他值,你会得到垃圾。 x = !!x 会将其布尔化为 0 或 1。

要使其独立于 2 的补码否定行为(其中 -1 已设置所有位,与 1 的补码或符号/幅度 C++ 实现不同),请使用无符号否定。

number ^= (-(unsigned long)x ^ number) & (1UL << n);

或者

unsigned long newbit = !!x;    // Also booleanize to force 0 or 1
number ^= (-newbit ^ number) & (1UL << n);

使用无符号类型进行可移植位操作通常是一个好主意。

或者

number = (number & ~(1UL << n)) | (x << n);

(number & ~(1UL << n)) 将清除第 n 位,(x << n) 将第 n 位设置为 x。

通常不要复制/粘贴代码也是一个好主意,因此许多人使用预处理器宏(如 the community wiki answer further down)或某种封装。

我想指出,在对位设置/清除具有本机支持的平台(例如,AVR 微控制器)上,只要 x 是,编译器通常会将“myByte |= (1 << x)”转换为本机位设置/清除指令一个常数,例如:(1 << 5),或 const unsigned x = 5。

位 = 数字 & (1 << x);除非 bit 的类型为 _Bool (),否则不会将 bit x 的值放入 bit。否则,bit = !!(number & (1 << x));将要..

你为什么不把最后一个改成bit = (number >> x) & 1

1 是一个 int 文字,它是有符号的。所以这里的所有操作都对有符号数进行操作,这在标准中没有很好的定义。该标准不保证二进制补码或算术移位,因此最好使用 1U。

我更喜欢 number = number & ~(1 << n) | (x << n); 将第 n 位更改为 x。

答2:

HuntsBot周刊–不定时分享成功产品案例,学习他们如何成功建立自己的副业–huntsbot.com

使用标准 C++ 库:std::bitset。

或 Boost 版本:boost::dynamic_bitset。

无需自己动手:

#include 
#include 

int main()
{
    std::bitset<5> x;

    x[1] = 1;
    x[2] = 0;
    // Note x[0-4]  valid

    std::cout << x << std::endl;
}

[Alpha:] > ./a.out
00010

与 standard library 编译时大小的位集相比,Boost 版本允许运行时大小的位集。

+1。并不是说 std::bitset 可以从“C”中使用,而是当作者用“C++”标记他/她的问题时,AFAIK,你的答案是最好的…… std::vector 是另一种方式,如果有人知道它的优点和缺点

@andrewdotnich:向量是(不幸的是)将值存储为位的专业化。有关详细信息,请参阅 gotw.ca/publications/mill09.htm...

也许没有人提到它,因为它被标记为嵌入。在大多数嵌入式系统中,您会像避免瘟疫一样避免 STL。在大多数嵌入式编译器中,增强支持可能是一种非常罕见的鸟。

@Martin 这是非常真实的。除了像 STL 和模板这样的特定性能杀手之外,许多嵌入式系统甚至完全避免使用整个标准库,因为它们很难验证。大多数嵌入式分支都采用 MISRA 等标准,这需要静态代码分析工具(顺便说一句,任何软件专业人员都应该使用此类工具,而不仅仅是嵌入式人员)。通常人们有比通过整个标准库运行静态分析更好的事情要做 - 如果它的源代码甚至在特定编译器上可供他们使用。

@Lundin:您的陈述过于宽泛(因此争论无用)。我确信我能找到情况是否属实。这不会改变我的初始点。这两个类都非常适合在嵌入式系统中使用(我知道它们被使用的事实)。您关于不在嵌入式系统上使用 STL/Boost 的初始观点也是错误的。我确信有些系统不使用它们,即使是确实使用它们的系统,它们也被明智地使用,但说它们没有被使用是不正确的(因为有系统被使用)。

答3:

huntsbot.com汇聚了国内外优秀的初创产品创意,可按收入、分类等筛选,希望这些产品与实践经验能给您带来灵感。

另一种选择是使用位域:

struct bits {
    unsigned int a:1;
    unsigned int b:1;
    unsigned int c:1;
};

struct bits mybits;

定义一个 3 位字段(实际上是三个 1 位字段)。位操作现在变得有点(哈哈)简单:

设置或清除位:

mybits.b = 1;
mybits.c = 0;

稍微切换一下:

mybits.a = !mybits.a;
mybits.b = ~mybits.b;
mybits.c ^= 1;  /* all work */

检查了一下:

if (mybits.c)  //if mybits.c is non zero the next line below will execute

这仅适用于固定大小的位字段。否则,您必须求助于之前文章中描述的位旋转技术。

我一直发现使用位域是个坏主意。您无法控制分配位的顺序(从顶部或底部),这使得无法以稳定/可移植的方式序列化值,除了一次位。将 DIY 位算术与位域混合也是不可能的,例如制作一个同时测试多个位的掩码。您当然可以使用 && 并希望编译器能正确优化它...

位域在很多方面都很糟糕,我几乎可以写一本关于它的书。事实上,对于一个需要 MISRA-C 合规性的现场项目,我几乎不得不这样做。 MISRA-C 强制记录所有实现定义的行为,因此我最终写了一篇关于位字段中可能出错的所有内容的文章。位顺序、字节顺序、填充位、填充字节、各种其他对齐问题、与位字段之间的隐式和显式类型转换、如果不使用 int 则为 UB 等等。相反,使用按位运算符来减少错误和可移植代码。位域是完全冗余的。

像大多数语言特性一样,位域可以正确使用,也可以被滥用。如果您需要将几个小值打包到一个 int 中,位域可能非常有用。另一方面,如果您开始假设位字段如何映射到实际包含的 int,那么您只是在自找麻烦。

@endolith:那不是一个好主意。你可以让它工作,但它不一定可以移植到不同的处理器、不同的编译器,甚至是同一编译器的下一个版本。

@Yasky 和 Ferruccio 对于这种方法的 sizeof() 得到了不同的答案,这应该说明不仅跨编译器而且跨硬件的兼容性问题。我们有时会自欺欺人,说我们已经用语言或定义的运行时解决了这些问题,但实际上归结为“它可以在我的机器上工作吗?”。你们嵌入的家伙有我的尊重(和同情)。

答4:

打造属于自己的副业,开启自由职业之旅,从huntsbot.com开始!

我使用头文件中定义的宏来处理位设置和清除:

/* a=target variable, b=bit number to act upon 0-n */
#define BIT_SET(a,b) ((a) |= (1ULL<<(b)))
#define BIT_CLEAR(a,b) ((a) &= ~(1ULL<<(b)))
#define BIT_FLIP(a,b) ((a) ^= (1ULL<<(b)))
#define BIT_CHECK(a,b) (!!((a) & (1ULL<<(b))))        // '!!' to make sure this returns 0 or 1

#define BITMASK_SET(x, mask) ((x) |= (mask))
#define BITMASK_CLEAR(x, mask) ((x) &= (~(mask)))
#define BITMASK_FLIP(x, mask) ((x) ^= (mask))
#define BITMASK_CHECK_ALL(x, mask) (!(~(x) & (mask)))
#define BITMASK_CHECK_ANY(x, mask) ((x) & (mask))

嗯,我意识到这是一篇已有 5 年历史的帖子,但这些宏中没有任何参数重复,Dan

BITMASK_CHECK(x,y) ((x) & (y)) 必须是 ((x) & (y)) == (y) 否则它会在多位掩码上返回不正确的结果(例如 5 与 3)/*向所有掘墓者致敬 :)*/

1 应该是 (uintmax_t)1 或类似的,以防有人试图在 long 或更大的类型上使用这些宏

BITMASK_CHECK_ALL(x,y) 可以实现为 !~((~(y))|(x))

@Handy999 在应用德摩根定律并重新安排获得 !(~(x) & (y)) 后,更容易理解为什么会这样

答5:

huntsbot.com – 高效赚钱,自由工作

有时值得使用 enum 来命名这些位:

enum ThingFlags = {
  ThingMask  = 0x0000,
  ThingFlag0 = 1 << 0,
  ThingFlag1 = 1 << 1,
  ThingError = 1 << 8,
}

然后稍后使用这些名称。即写

thingstate |= ThingFlag1;
thingstate &= ~ThingFlag0;
if (thing & ThingError) {...}

设置、清除和测试。这样,您就可以从其余代码中隐藏幻数。

除此之外,我支持杰里米的解决方案。

或者,您可以创建一个 clearbits() 函数而不是 &= ~。你为什么要为此使用枚举?我认为这些是为了创建一堆具有隐藏任意值的唯一变量,但是您为每个变量分配了一个确定的值。那么与将它们定义为变量相比有什么好处呢?

@endolith:在 c 编程中使用 enum 表示相关常量集可以追溯到很久以前。我怀疑现代编译器与 const short 或其他任何东西相比的唯一优势是它们被明确地组合在一起。当您想要它们用于位掩码之外的other 时,您将获得自动编号。当然,在 c++ 中,它们也形成了不同的类型,为您提供了一些额外的静态错误检查。

如果您没有为位的每个可能值定义一个常量,您将进入未定义的枚举常量。例如,ThingError|ThingFlag1 的 enum ThingFlags 值是多少?

如果您使用此方法,请记住枚举常量始终是有符号类型 int。由于隐式整数提升或有符号类型的按位运算,这可能会导致各种微妙的错误。 thingstate = ThingFlag1 >> 1 将例如调用实现定义的行为。 thingstate = (ThingFlag1 >> x) << y 可以调用未定义的行为。等等。为了安全起见,请始终强制转换为无符号类型。

@Lundin:从 C++11 开始,您可以设置枚举的基础类型,例如:enum My16Bits: unsigned short { ... };

答6:

huntsbot.com – 高效赚钱,自由工作

来自 snip-c.zip 的 bitops.h:

/*
**  Bit set, clear, and test operations
**
**  public domain snippet by Bob Stout
*/

typedef enum {ERROR = -1, FALSE, TRUE} LOGICAL;

#define BOOL(x) (!(!(x)))

#define BitSet(arg,posn) ((arg) | (1L << (posn)))
#define BitClr(arg,posn) ((arg) & ~(1L << (posn)))
#define BitTst(arg,posn) BOOL((arg) & (1L << (posn)))
#define BitFlp(arg,posn) ((arg) ^ (1L << (posn)))

好吧,我们来分析一下……

在所有这些中您似乎遇到问题的常见表达是“(1L <<(posn))”。所有这一切都是创建一个带有单个位的掩码,它适用于任何整数类型。 “posn”参数指定您想要该位的位置。如果 posn==0,则此表达式的计算结果为:

0000 0000 0000 0000 0000 0000 0000 0001 binary.

如果 posn==8,它将评估为:

0000 0000 0000 0000 0000 0001 0000 0000 binary.

换句话说,它只是在指定位置创建一个 0 和 1 的字段。唯一棘手的部分是在 BitClr() 宏中,我们需要在 1 的字段中设置单个 0 位。这是通过使用波浪号 (~) 运算符表示的相同表达式的 1 补码来实现的。

创建掩码后,它会按照您的建议应用到参数,方法是使用按位和 (&)、或 (|) 和异或 (^) 运算符。由于掩码是 long 类型,因此宏在 char、short、int 或 long 上同样有效。

底线是,这是对一整类问题的通用解决方案。当然,每次需要时都可以使用显式掩码值重写任何这些宏的等效项,甚至是适当的,但为什么要这样做呢?请记住,宏替换发生在预处理器中,因此生成的代码将反映编译器认为这些值是常量这一事实 - 即,每次需要时使用通用宏与“重新发明轮子”一样有效做位操作。

不服气?这是一些测试代码 - 我使用 Watcom C 进行了全面优化,但没有使用 _cdecl,因此生成的反汇编将尽可能干净:

----[ TEST.C ]------------------------------------------ ----------------------

#define BOOL(x) (!(!(x)))

#define BitSet(arg,posn) ((arg) | (1L << (posn)))
#define BitClr(arg,posn) ((arg) & ~(1L << (posn)))
#define BitTst(arg,posn) BOOL((arg) & (1L << (posn)))
#define BitFlp(arg,posn) ((arg) ^ (1L << (posn)))

int bitmanip(int word)
{
      word = BitSet(word, 2);
      word = BitSet(word, 7);
      word = BitClr(word, 3);
      word = BitFlp(word, 9);
      return word;
}

----[ TEST.OUT(拆机)]-------------------------------------------------- ---------

Module: C:\BINK\tst.c
Group: 'DGROUP' CONST,CONST2,_DATA,_BSS

Segment: _TEXT  BYTE   00000008 bytes  
 0000  0c 84             bitmanip_       or      al,84H    ; set bits 2 and 7
 0002  80 f4 02                          xor     ah,02H    ; flip bit 9 of EAX (bit 1 of AH)
 0005  24 f7                             and     al,0f7H
 0007  c3                                ret     

No disassembly errors

----[ 完结 ]-------------------------------------------- ----------------------

关于此的两件事:(1)在仔细阅读您的宏时,有些人可能错误地认为宏实际上在 arg 中设置/清除/翻转位,但是没有分配; (2) 你的 test.c 不完整;我怀疑如果您运行更多案例,您会发现问题(读者练习)

-1 这只是奇怪的混淆。永远不要通过隐藏宏背后的语言语法来重新发明 C 语言,这是非常糟糕的做法。然后是一些奇怪的东西:首先,1L 是有符号的,这意味着所有位操作都将在有符号类型上执行。传递给这些宏的所有内容都将以有符号长返回。不好。其次,这将在较小的 CPU 上非常低效,因为当操作可能在 int 级别时它会执行很长时间。第三,类函数宏是万恶之源:你没有任何类型安全性。此外,之前关于没有分配的评论非常有效。

如果 arg 是 long long,这将失败。 1L 需要是最广泛的类型,所以 (uintmax_t)1 。 (您可能会逃脱 1ull)

您是否针对代码大小进行了优化?在 Intel 主流 CPU 上,在此函数返回后读取 AX 或 EAX 时会出现部分寄存器停顿,因为它写入 EAX 的 8 位组件。 (在 AMD CPU 或其他不将部分寄存器与完整寄存器分开重命名的 CPU 上很好。Haswell/Skylake don't rename AL separately, but they do rename AH.)。

答7:

huntsbot.com汇聚了国内外优秀的初创产品创意,可按收入、分类等筛选,希望这些产品与实践经验能给您带来灵感。

对于初学者,我想用一个例子来解释一下:

例子:

value is 0x55;
bitnum : 3rd.

& 运算符用于检查位:

0101 0101
&
0000 1000
___________
0000 0000 (mean 0: False). It will work fine if the third bit is 1 (then the answer will be True)

切换或翻转:

0101 0101
^
0000 1000
___________
0101 1101 (Flip the third bit without affecting other bits)

| 运算符:设置位

0101 0101
|
0000 1000
___________
0101 1101 (set the third bit without affecting other bits)

答8:

huntsbot.com精选全球7大洲远程工作机会,涵盖各领域,帮助想要远程工作的数字游民们能更精准、更高效的找到对方。

由于这被标记为“嵌入式”,我假设您使用的是微控制器。以上所有建议都是有效且有效的(读取-修改-写入、联合、结构等)。

然而,在一次基于示波器的调试过程中,我惊讶地发现,与将值直接写入微控制器的 PORTnSET / PORTnCLEAR 寄存器相比,这些方法在 CPU 周期中具有相当大的开销,这在存在紧密循环/高电平的情况下产生了真正的差异-频率 ISR 的切换引脚。

对于那些不熟悉的人:在我的示例中,微控制器有一个反映输出引脚的通用引脚状态寄存器 PORTn,因此执行 PORTn |= BIT_TO_SET 会导致对该寄存器的读取-修改-写入。但是,PORTnSET / PORTnCLEAR 寄存器采用“1”表示“请将此位设为 1”(SET)或“请将此位设为 0”(CLEAR),而“0”表示“不要管该引脚”。因此,您最终会得到两个端口地址,具体取决于您是设置还是清除该位(并不总是方便),但反应更快,汇编代码更小。

Micro 是 Coldfire MCF52259,在 Codewarrior 中使用 C。查看反汇编程序/asm 是一个有用的练习,因为它显示了 CPU 必须执行的所有步骤,即使是最基本的操作。 我们还在时间关键的循环中发现了其他占用 CPU 的指令 - 通过执行 var %= max_val 来约束变量每次都会花费大量 CPU 周期,而执行 if(var > max_val)var-=max_val只使用几条指令。 这里有更多技巧的好指南:codeproject.com/Articles/6154/…

更重要的是,辅助内存映射 I/O 寄存器提供了一种原子更新机制。如果序列中断,读取/修改/写入可能会非常糟糕。

请记住,所有端口寄存器都将定义为 volatile,因此编译器无法对涉及此类寄存器的代码执行任何优化。因此,反汇编此类代码并查看它在汇编程序级别上的结果是一种很好的做法。

答9:

huntsbot.com全球7大洲远程工作机会,探索不一样的工作方式

这是我最喜欢的位算术宏,它适用于从 unsigned char 到 size_t 的任何类型的无符号整数数组(这是应该有效使用的最大类型):

#define BITOP(a,b,op) \
 ((a)[(size_t)(b)/(8*sizeof *(a))] op ((size_t)1<<((size_t)(b)%(8*sizeof *(a)))))

设置一点:

BITOP(array, bit, |=);

要清除一点:

BITOP(array, bit, &=~);

稍微切换一下:

BITOP(array, bit, ^=);

测试一下:

if (BITOP(array, bit, &)) ...

等等

阅读很好,但应该注意可能的副作用。在循环中使用 BITOP(array, bit++, |=); 很可能不会执行调用者想要的操作。

的确。 =) 您可能更喜欢的一种变体是将它分成 2 个宏,1 个用于寻址数组元素,另一个用于将位移动到位,ala BITCELL(a,b) |= BITMASK(a,b);(两者都将 a 作为参数来确定大小,但后者永远不会评估 a,因为它只出现在 sizeof 中)。

@R .. 这个答案真的很旧,但在这种情况下,我可能更喜欢函数而不是宏。

次要:第 3 个 (size_t) 演员似乎只是为了确保某些 unsigned math 与 %。可以(unsigned)在那里。

(size_t)(b)/(8*sizeof *(a)) 在除法之前可能会不必要地缩小 b。只有非常大的位数组存在问题。仍然是一个有趣的宏。

答10:

huntsbot.com高效搞钱,一站式跟进超10+任务平台外包需求

位域方法在嵌入式领域还有其他优势。您可以定义一个直接映射到特定硬件寄存器中的位的结构。

struct HwRegister {
    unsigned int errorFlag:1;  // one-bit flag field
    unsigned int Mode:3;       // three-bit mode field
    unsigned int StatusCode:4;  // four-bit status code
};

struct HwRegister CR3342_AReg;

您需要注意位打包顺序 - 我认为它首先是 MSB,但这可能取决于实现。此外,验证您的编译器处理程序如何跨越字节边界。

然后,您可以像以前一样读取、写入、测试各个值。

几乎所有关于位域的东西都是实现定义的。即使您设法找到有关特定编译器如何实现它们的所有详细信息,在您的代码中使用它们肯定会使其不可移植。

@Lundin - 是的,但是嵌入式系统的位摆弄(特别是在硬件寄存器中,这是我的回答所涉及的)无论如何都不会有用。

也许不是在完全不同的 CPU 之间。但是您很可能希望它在编译器之间和不同项目之间具有可移植性。并且有很多嵌入式的“bit-fiddling”根本与硬件无关,例如数据协议编码/解码。

...如果您养成使用位域进行嵌入式编程的习惯,您会发现您的 X86 代码运行得更快,也更精简。不是在简单的基准测试中,您需要整台机器来粉碎基准测试,而是在程序竞争资源的现实世界多任务环境中。优势 CISC - 其最初的设计目标是弥补 CPU 比总线快和内存慢的问题。

答11:

huntsbot.com聚合了超过10+全球外包任务平台的外包需求,寻找外包任务与机会变的简单与高效。

让我们先假设几件事 num = 55 整数来执行按位操作(设置、获取、清除、切换)。 n = 4 0 基于位位置以执行按位运算。

如何获得一点?

得到 num 的第 n 位右移 num,n 次。然后用 1 执行按位与 &。

bit = (num >> n) & 1;

这个怎么运作?

       0011 0111 (55 in decimal)
    >>         4 (right shift 4 times)
-----------------
       0000 0011
     & 0000 0001 (1 in decimal)
-----------------
    => 0000 0001 (final result)

怎么设置一点?

设置特定的数字位。左移 1 n 次。然后执行按位 OR |与 num 的操作。

num |= (1 << n);    // Equivalent to; num = (1 << n) | num;

这个怎么运作?

       0000 0001 (1 in decimal)
    <<         4 (left shift 4 times)
-----------------
       0001 0000
     | 0011 0111 (55 in decimal)
-----------------
    => 0001 0000 (final result)

怎么清除一点?

左移 1,n 次,即 1 << n。对上述结果进行按位补码。这样第 n 位变为未设置,其余位变为设置,即 ~ (1 << n)。最后,对上面的结果和 num 进行按位与 & 运算。上面三个步骤一起可以写成 num & (~ (1 << n));

https://i.stack.imgur.com/mf8hm.png

num &= (~(1 << n));    // Equivalent to; num = num & (~(1 << n));

这个怎么运作?

       0000 0001 (1 in decimal)
    <<         4 (left shift 4 times)
-----------------
     ~ 0001 0000
-----------------
       1110 1111
     & 0011 0111 (55 in decimal)
-----------------
    => 0010 0111 (final result)

如何切换一点?

为了切换位,我们使用按位 XOR ^ 运算符。如果两个操作数的对应位不同,则按位异或运算符计算为 1,否则计算为 0。

这意味着切换一个位,我们需要对要切换的位和 1 执行 XOR 操作。

num ^= (1 << n);    // Equivalent to; num = num ^ (1 << n);

这个怎么运作?

如果要切换的位为 0,则 0 ^ 1 => 1。

如果要切换的位为 1,则 1 ^ 1 => 0。

       0000 0001 (1 in decimal)
    <<         4 (left shift 4 times)
-----------------
       0001 0000
     ^ 0011 0111 (55 in decimal)
-----------------
    => 0010 0111 (final result)

推荐阅读 - Bitwise operator exercises

感谢您的详细解释。这是 BIT Magic link 练习题的链接

原文链接:https://www.huntsbot.com/qa/9rDV/how-do-i-set-clear-and-toggle-a-single-bit?lang=zh_CN&from=csdn

huntsbot.com高效搞钱,一站式跟进超10+任务平台外包需求

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值