题 你如何设置,清除和切换一个位?


如何在C / C ++中设置,清除和切换?


2053
2017-09-07 00:42


起源


读这个: graphics.stanford.edu/~seander/bithacks.html 而且,当你掌握了这一点时,请阅读以下内容: realtimecollisiondetection.net/blog/?p=78 - ugasoft
您可能也有兴趣退房 Bit Twiddler, 比特扭曲黑客,和 聚合魔术算法。
这个链接帮助我理解这些操作实际上是如何工作的 - cs.umd.edu/class/sum2003/cmsc311/Notes/BitOp/setBitI.html 在这里你可以找到更有趣的操作 - cs.umd.edu/class/sum2003/cmsc311/Notes - rajya vardhan
我很容易想象这不是问题本身,而是产生一个非常有用的参考指南。考虑到我在需要一些信息时到了这里,无论如何。 - Joey van Hummel
@glglgl / Jonathon它有足够的意义让C ++被标记为这样。这是一个具有大量流量的历史性问题,C ++标签将帮助感兴趣的程序员通过谷歌搜索找到它。 - Luchian Grigore


答案:


设置一下

使用按位OR运算符(|)设置一下。

number |= 1UL << n;

这将设置 n一点点 number

使用 1ULL 如果 number 比...宽 unsigned long;促进 1UL << n 直到评估后才会发生 1UL << n 它的未定义行为偏移超过a的宽度 long。这同样适用于所有其他示例。

清理一下

使用按位AND运算符(&)清楚一点。

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

这将清除 n一点点 number。必须使用按位NOT运算符反转位串(~),然后和它。

切换了一下

XOR运算符(^)可以用来切换一下。

number ^= 1UL << n;

这将切换 n一点点 number

检查一下

你没有要求这个,但我不妨补充一下。

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

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

这将是值得的 n一点点 number 进入变量 bit

改变了 ñ有点儿 X

设置 n两点都有 1 要么 0 可以通过以下2的补码C ++实现来实现:

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

n 将被设置如果 x 是 1,如果 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);

使用无符号类型进行便携式位操作通常是个好主意。

一般来说,通常不要复制/粘贴代码也是一个好主意,所以很多人都使用预处理器宏(比如 社区维基进一步回答)或某种封装。


3003
2017-09-07 00:50



我想要注意的是,在本机支持bit set / clear(例如AVR微控制器)的平台上,只要x为x,编译器就会经常将'myByte | =(1 << x)'转换为本机位设置/清除指令常数,ex:(1 << 5),或const unsigned x = 5。 - Aaron
bit = number&(1 << x);除非bit具有_Bool类型(<stdbool.h>),否则不会将位x的值放入位。否则,bit = !!(数字&(1 << x));将.. - Chris
你为什么不把最后一个改成 bit = (number >> x) & 1 - aaronman
1 是一个 int 文字,签名。因此,此处的所有操作都使用有符号数进行操作,而这些数字并未由标准定义。标准不保证两个补码或算术移位,因此最好使用 1U。 - Siyuan Ren
我更喜欢 number = number & ~(1 << n) | (x << n); 用于将第n位更改为x。 - Eliko


使用标准C ++库: std::bitset<N>

或者 促进 版: boost::dynamic_bitset

没有必要自己动手:

#include <bitset>
#include <iostream>

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

与a相比,Boost版本允许运行时大小的bitset 标准库 编译时大小的bitset。


383
2017-09-18 00:34



+1。不是说std​​ :: bitset可以从“C”中使用,但是当作者用“C ++”,AFAIK标记他/她的问题时,你的答案是最好的... std :: vector <bool>是另一种方式,如果一个人知道它的优点和缺点 - paercebal
@andrewdotnich:vector <bool>是(不幸的)一个将值存储为位的特化。看到 gotw.ca/publications/mill09.htm 了解更多信息...... - Niklas
也许没有人提到它,因为它被标记为嵌入式。在大多数嵌入式系统中,您可以像瘟疫一样避免STL。在大多数嵌入式编译器中,增强支持可能是一种非常罕见的鸟类。 - Lundin
@Martin这是非常真实的。除了像STL和模板这样的特定性能杀手之外,许多嵌入式系统甚至完全避开了整个标准库,因为它们很难验证。大多数嵌入式分支都采用MISRA等标准,这需要静态代码分析工具(任何软件专业人员都应该使用这些工具,而不仅仅是嵌入式人员)。通常人们比通过整个标准库运行静态分析有更好的事情 - 如果它的源代码甚至可以在特定的编译器上使用它们。 - Lundin
@Lundin:你的陈述过于宽泛(因此没有争议)。我相信我能找到他们是真实的情况。这不会改变我的初始观点。这两个类都非常适合在嵌入式系统中使用(我知道它们被使用的事实)。关于未在嵌入式系统上使用STL / Boost的初始观点也是错误的。我确信有些系统不使用它们,甚至使用它们的系统也会明智地使用它们但是说它们没有被使用是不正确的(因为有系统使用它们)。 - Martin York


另一种选择是使用位字段:

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

这仅适用于固定大小的位字段。否则你必须采用之前帖子中描述的比特技巧。


213
2017-09-11 00:56



我总是发现使用位域是一个坏主意。您无法控制分配位的顺序(从顶部或底部),这使得无法以稳定/可移植的方式将值序列化,除了一次一位。将位运算与位域混合也是不可能的,例如制作一次测试几位的掩码。您当然可以使用&&并希望编译器能够正确地优化它... - R..
比特字段在很多方面都很糟糕,我几乎可以写一本关于它的书。事实上,我几乎不得不为需要符合MISRA-C标准的现场计划做到这一点。 MISRA-C强制执行所有实现定义的行为,因此我最后写了一篇关于位域中可能出错的所有内容的文章。位顺序,字节顺序,填充位,填充字节,各种其他对齐问题,与位字段之间的隐式和显式类型转换,如果未使用int,则为UB,等等。相反,使用按位运算符可以减少错误和可移植代码。位字段完全是冗余的。 - Lundin
与大多数语言功能一样,位字段可以正确使用,也可以滥用。如果需要将几个小值打包到单个int中,则位字段非常有用。另一方面,如果您开始假设位字段如何映射到实际的包含int,那么您只是在寻找麻烦。 - Ferruccio
@endolith:那不是个好主意。你可以使它工作,但它不一定可以移植到不同的处理器,或不同的编译器,甚至可以移植到同一编译器的下一个版本。 - Ferruccio
@R。可以同时使用两者 struct 可以放在一个(通常是匿名的) union 有一个整数等它有效。 (我意识到这是一个老线程btw) - Shade


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

/* 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)))

/* x=target variable, y=mask */
#define BITMASK_SET(x,y) ((x) |= (y))
#define BITMASK_CLEAR(x,y) ((x) &= (~(y)))
#define BITMASK_FLIP(x,y) ((x) ^= (y))
#define BITMASK_CHECK_ALL(x,y) (((x) & (y)) == (y))   // warning: evaluates y twice
#define BITMASK_CHECK_ANY(x,y) ((x) & (y))

125
2017-09-08 21:07



呃我意识到这是一个5岁的帖子,但任何这些宏都没有任何重复,Dan - Robert Kelly
BITMASK_CHECK(x,y) ((x) & (y)) 一定是 ((x) & (y)) == (y) 否则它会在多位掩码上返回不正确的结果(例如 5 与 3)/ *对所有掘墓人员问好:)* / - brigadir
1 应该 (uintmax_t)1 或者类似的,以防任何人试图在a上使用这些宏 long 或更大的类型 - M.M
要么 1ULL 同样有效 (uintmax_t) 在大多数实现。 - Peter Cordes
@brigadir:取决于您是要检查任何位集还是设置所有位。我更新了答案以包含描述性名称。 - Peter Cordes


有时值得使用 enum 至 名称 比特:

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

然后使用  稍后的。即写

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

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

除此之外,我赞同杰里米的解决方案。


99
2017-09-17 02:04



或者你可以做一个 clearbits() 功能而不是 &= ~。你为什么要使用枚举?我认为这些是用于创建一组具有隐藏任意值的唯一变量,但是您为每个变量分配了一个确定的值。那么将它们定义为变量的好处是什么呢? - endolith
@endolith:使用 enum用于相关常量集的s在c编程中可以追溯到很长一段时间。我怀疑与现代编译器相比,唯一的优势 const short 或者他们被明确地组合在一起的任何东西。而当你想要他们的东西 其他 比bitmasks你得到自动编号。当然,在c ++中,它们也形成了不同的类型,为您提供了一些额外的静态错误检查。 - dmckee
如果没有为每个可能的位值定义一个常量,那么你将进入未定义的枚举常量。什么是 enum ThingFlags 的价值 ThingError|ThingFlag1, 例如? - Luis Colorado
如果您使用此方法,请记住枚举常量始终是有符号类型 int。由于对已签名类型的隐式整数提升或按位运算,这可能会导致各种微妙的错误。 thingstate = ThingFlag1 >> 1例如,将调用实现定义的行为。 thingstate = (ThingFlag1 >> x) << y 可以调用未定义的行为。等等。为安全起见,始终强制转换为无符号类型。 - Lundin
@Lundin:从C ++ 11开始,您可以设置枚举的基础类型,例如: enum My16Bits: unsigned short { ... }; - Aiken Drum


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来完成的 由波浪号(〜)运算符表示的相同表达式的补码。

一旦创建了蒙版,它就像你建议的那样应用于参数, 通过使用按位和(&),或(|)和xor(^)运算符。自面具 类型为long,宏也可以在char,short,int上工作, 或者长的。

最重要的是,这是整个类的一般解决方案 问题。当然,重写它是可能的,甚至是恰当的 每当您使用显式掩码值时,相当于任何这些宏 需要一个,但为什么呢?请记住,宏替换发生在 预处理器等生成的代码将反映出值的事实 编译器认为它是恒定的 - 即使用它同样有效 每次你需要做的“重新发明轮子”的通用宏 位操纵。

不服气?这是一些测试代码 - 我使用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(disassembled)] -------------------------------------- ---------

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

---- [finis] ------------------------------------------- ----------------------


34
2018-06-05 14:18



关于这一点的两件事:(1)在仔细阅读你的宏时,有些人可能错误地认为宏实际上是在arg中设置/清除/翻转位,但是没有赋值; (2)你的test.c不完整;我怀疑如果你运行更多的案例你会发现问题(读者练习) - Dan
-1这只是一种奇怪的混淆。永远不要通过隐藏宏背后的语言语法来重新发明C语言,它是 非常 不好的做法。然后是一些奇怪的事情:首先,1L被签名,这意味着所有位操作都将在签名类型上执行。传递给这些宏的所有内容都将以signed long形式返回。不好。其次,这对于较小的CPU来说效率非常低,因为当操作可能处于int级别时,它会执行很长时间。第三,类似函数的宏是所有邪恶的根源:你没有任何类型安全。此外,之前关于没有分配的评论非常有效。 - Lundin
如果这将失败 arg 是 long long。 1L 需要是最广泛的类型,所以 (uintmax_t)1 。 (你可能会侥幸逃脱 1ull) - M.M
您是否针对代码大小进行了优化?在Intel主流CPU上,在此函数返回后读取AX或EAX时会出现部分寄存器停顿,因为它会写入EAX的8位组件。 (在AMD CPU或其他不会将部分寄存器与完整寄存器分开重命名的情况下都可以。 Haswell / Skylake不会单独重命名AL,但他们会重命名AH。)。 - Peter Cordes


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

例:

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)

| operator:设置位

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

29
2017-09-07 00:45





使用按位运算符: &  | 

设置最后一位 000b

foo = foo | 001b

检查最后一位 foo

if ( foo & 001b ) ....

要清除最后一位 foo

foo = foo & 110b

我用了 XXXb 为清楚起见。您可能正在使用HEX表示,具体取决于您打包位的数据结构。


26
2017-07-13 06:53



C中没有二进制表示法。二进制整数常量是非标准扩展。 - Lundin