How do you set, clear, and toggle a single bit?
如何在C/C++中设置、清除和切换一点?
设置位
使用按位或运算符(
1 | number |= 1UL << n; |
这将设置
如果
使用位与运算符(
1 | number &= ~(1UL << n); |
这将清除
xor运算符(
1 | number ^= 1UL << n; |
这将切换
你没有要求,但我还是加上一点。
要检查一点,将数字n右移,然后按位和它:
1 | bit = (number >> n) & 1U; |
这将把
将EDOCX1第7位第10位设置为EDCOX1、10或EDCOX1×2,可在2的补码C++实现中实现:
1 | number ^= (-x ^ number) & (1UL << n); |
如果
为了使它独立于2的补码否定行为(其中EDCOX1×39)已经设置了所有位,与1的补码或符号/大小C++实现不同,使用无符号否定。
1 | number ^= (-(unsigned long)x ^ number) & (1UL << n); |
或
1 2 | unsigned long newbit = !!x; // Also booleanize to force 0 or 1 number ^= (-newbit ^ number) & (1UL << n); |
使用无符号类型进行可移植的位操作通常是一个好主意。
或
1 | number = (number & ~(1UL << n)) | (x << n); |
一般来说,不复制/粘贴代码也是一个好主意,很多人使用预处理器宏(比如下面的社区wiki答案)或某种封装。
使用标准C++库:EDCOX1×0。
或者是增强版:
不需要自己滚:
1 2 3 4 5 6 7 8 9 10 11 12 13 | #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; } |
1 2 | [Alpha:] > ./a.out 00010 |
与标准库编译时大小的位集相比,Boost版本允许运行时大小的位集。
另一种选择是使用位字段:
1 2 3 4 5 6 7 | struct bits { unsigned int a:1; unsigned int b:1; unsigned int c:1; }; struct bits mybits; |
定义一个3位字段(实际上是三个1位FELD)。位操作现在变得简单了一点(haha):
设置或清除一点:
1 2 | mybits.b = 1; mybits.c = 0; |
要切换一点:
1 2 3 | mybits.a = !mybits.a; mybits.b = ~mybits.b; mybits.c ^= 1; /* all work */ |
检查一点:
1 | if (mybits.c) //if mybits.c is non zero the next line below will execute |
这只适用于固定大小的位字段。否则,你必须借助于前几篇文章中描述的位旋转技术。
我使用头文件中定义的宏来处理位集和清除:
1 2 3 4 5 6 7 8 9 10 11 12 | /* 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 /* 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)) |
有时值得使用
1 2 3 4 5 6 | enum ThingFlags = { ThingMask = 0x0000, ThingFlag0 = 1 << 0, ThingFlag1 = 1 << 1, ThingError = 1 << 8, } |
然后在后面使用这些名称。即写
1 2 3 | thingstate |= ThingFlag1; thingstate &= ~ThingFlag0; if (thing & ThingError) {...} |
设置、清除和测试。这样,您就可以将魔法数字隐藏在代码的其余部分。
除此之外,我赞同杰里米的解决方案。
从snip-c.zip的bitops.h:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | /* ** 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,则此表达式将评价为:
1 | 0000 0000 0000 0000 0000 0000 0000 0001 binary. |
如果posn==8,它将计算为
1 | 0000 0000 0000 0000 0000 0001 0000 0000 binary. |
换句话说,它只创建一个0的字段,其中1位于指定的位置。唯一棘手的部分是bitclr()宏,我们需要在其中设置字段1中的单个0位。这是通过使用1来完成的。用颚化符(~)运算符表示的同一表达式的补码。
一旦创建了遮罩,它就会按照您的建议应用于参数,使用位与(&;)或()和xor(^)运算符。自从面具之后是long类型,宏在char、short、int和或是长的。
最重要的是,这是对整个类问题。当然,重写每当您需要一个,但为什么要这样做?记住,宏替换发生在预处理器,因此生成的代码将反映以下事实:被编译器视为常量-也就是说,使用每当你需要做的时候,你都可以使用通用的宏来"重新发明轮子"。钻头操作。
不服气?以下是一些测试代码-我使用了watcom C并进行了完全优化如果不使用_cdecl,则所产生的拆卸将与可能的:
——[测试c]——————————————————————————————————————————————————————————————————————————————————————————————————————————————————--
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | #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; } |
----[测试输出(已分解)]----------------------------------------
1 2 3 4 5 6 7 8 9 10 | 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 | foo = foo | 001b |
检查
1 | if ( foo & 001b ) .... |
清除
1 | foo = foo & 110b |
为了清晰起见,我使用了
对于初学者,我想用一个例子解释一下:
例子:
1 2 | value is 0x55; bitnum : 3rd. |
使用
1 2 3 4 5 | 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) |
切换或翻转:
1 2 3 4 5 | 0101 0101 ^ 0000 1000 ___________ 0101 1101 (Flip the third bit without affecting other bits) |
1 2 3 4 5 | 0101 0101 | 0000 1000 ___________ 0101 1101 (set the third bit without affecting other bits) |
这是我最喜欢的位算术宏,它适用于从
1 2 | #define BITOP(a,b,op) \ ((a)[(size_t)(b)/(8*sizeof *(a))] op ((size_t)1<<((size_t)(b)%(8*sizeof *(a))))) |
设置一点:
1 | BITOP(array, bit, |=); |
澄清一点:
1 | BITOP(array, bit, &=~); |
要切换一点:
1 | BITOP(array, bit, ^=); |
测试一点:
1 | if (BITOP(array, bit, &)) ... |
等。
Bitfield方法在嵌入式领域还有其他优势。可以定义直接映射到特定硬件寄存器中位的结构。
1 2 3 4 5 6 7 | 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,但这可能与实现有关。另外,验证编译器如何处理跨越字节边界的字段。
然后您可以像以前一样读取、写入和测试各个值。
因为它被标记为"嵌入式",所以我假设您使用的是微控制器。以上所有建议都是有效的(读-修改-写、联合、结构等)。
然而,在一次基于示波器的调试过程中,我惊奇地发现,与直接将值写入Micro的portnset/portnclear寄存器相比,这些方法在CPU周期中有相当大的开销,这在存在紧密循环/高频ISR的切换管脚的情况下会产生真正的差异。
对于不熟悉的人:在我的例子中,micro有一个反映输出管脚的通用管脚状态寄存器portn,因此,portn=bit to _set会导致对该寄存器的读-修改-写操作。但是,portnset/portnclear寄存器采用"1"表示"请将此位设为1"(设置)或"请将此位设为零"(清除),而"0"表示"别管脚"。因此,您最终得到了两个端口地址,这取决于您是在设置还是清除位(并不总是方便的),而是更快的反应和更小的汇编代码。
更一般的,对于任意大小的位图:
1 2 3 4 | #define BITS 8 #define BIT_SET( p, n) (p[(n)/BITS] |= (0x80>>((n)%BITS))) #define BIT_CLEAR(p, n) (p[(n)/BITS] &= ~(0x80>>((n)%BITS))) #define BIT_ISSET(p, n) (p[(n)/BITS] & (0x80>>((n)%BITS))) |
在任意类型变量的任意位置检查位:
1 | #define bit_test(x, y) ( ( ((const char*)&(x))[(y)>>3] & 0x80 >> ((y)&0x07)) >> (7-((y)&0x07) ) ) |
样品使用情况:
1 2 3 4 5 6 7 8 9 10 | int main(void) { unsigned char arr[8] = { 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF }; for (int ix = 0; ix < 64; ++ix) printf("bit %d is %d ", ix, bit_test(arr, ix)); return 0; } |
笔记:这是设计为快速(考虑到它的灵活性)和非分支。在编译Sun Studio 8时,它会产生高效的SPARC机器代码;我还使用AMD64上的MSVC++2008测试了它。可以制作类似的宏来设置和清除位。与其他许多解决方案相比,此解决方案的关键区别在于它适用于几乎任何类型变量中的任何位置。
如果你在做很多琐碎的事情,你可能会想用面具,这会使整个事情更快。以下函数速度非常快,而且仍然很灵活(它们允许在任意大小的位图中进行位旋转)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 | const unsigned char TQuickByteMask[8] = { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, }; /** Set bit in any sized bit mask. * * @return none * * @param bit - Bit number. * @param bitmap - Pointer to bitmap. */ void TSetBit( short bit, unsigned char *bitmap) { short n, x; x = bit / 8; // Index to byte. n = bit % 8; // Specific bit in byte. bitmap[x] |= TQuickByteMask[n]; // Set bit. } /** Reset bit in any sized mask. * * @return None * * @param bit - Bit number. * @param bitmap - Pointer to bitmap. */ void TResetBit( short bit, unsigned char *bitmap) { short n, x; x = bit / 8; // Index to byte. n = bit % 8; // Specific bit in byte. bitmap[x] &= (~TQuickByteMask[n]); // Reset bit. } /** Toggle bit in any sized bit mask. * * @return none * * @param bit - Bit number. * @param bitmap - Pointer to bitmap. */ void TToggleBit( short bit, unsigned char *bitmap) { short n, x; x = bit / 8; // Index to byte. n = bit % 8; // Specific bit in byte. bitmap[x] ^= TQuickByteMask[n]; // Toggle bit. } /** Checks specified bit. * * @return 1 if bit set else 0. * * @param bit - Bit number. * @param bitmap - Pointer to bitmap. */ short TIsBitSet( short bit, const unsigned char *bitmap) { short n, x; x = bit / 8; // Index to byte. n = bit % 8; // Specific bit in byte. // Test bit (logigal AND). if (bitmap[x] & TQuickByteMask[n]) return 1; return 0; } /** Checks specified bit. * * @return 1 if bit reset else 0. * * @param bit - Bit number. * @param bitmap - Pointer to bitmap. */ short TIsBitReset( short bit, const unsigned char *bitmap) { return TIsBitSet(bit, bitmap) ^ 1; } /** Count number of bits set in a bitmap. * * @return Number of bits set. * * @param bitmap - Pointer to bitmap. * @param size - Bitmap size (in bits). * * @note Not very efficient in terms of execution speed. If you are doing * some computationally intense stuff you may need a more complex * implementation which would be faster (especially for big bitmaps). * See (http://graphics.stanford.edu/~seander/bithacks.html). */ int TCountBits( const unsigned char *bitmap, int size) { int i, count = 0; for (i=0; i<size; i++) if (TIsBitSet(i, bitmap)) count++; return count; } |
注意,要在16位整数中设置位"n",请执行以下操作:
1 | TSetBit( n, &my_int); |
由您来确保位号在您通过的位映射的范围内。请注意,对于字节、字、双字、qwords等的小endian处理器,它们在内存中相互正确映射(小endian处理器比big endian处理器"更好"的主要原因是,啊,我感到一场火焰大战即将来临…)。
此程序用于将任何数据位从0更改为1或1更改为0:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | { unsigned int data = 0x000000F0; int bitpos = 4; int bitvalue = 1; unsigned int bit = data; bit = (bit>>bitpos)&0x00000001; int invbitvalue = 0x00000001&(~bitvalue); printf("%x ",bit); if (bitvalue == 0) { if (bit == 0) printf("%x ", data); else { data = (data^(invbitvalue<<bitpos)); printf("%x ", data); } } else { if (bit == 1) printf("elseif %x ", data); else { data = (data|(bitvalue<<bitpos)); printf("else %x ", data); } } } |
使用此:
1 2 3 4 5 6 7 8 9 | int ToggleNthBit ( unsigned char n, int num ) { if(num & (1 << n)) num &= ~(1 << n); else num |= (1 << n); return num; } |
扩展
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | #include <iostream> #include <bitset> #include <string> using namespace std; int main() { bitset<8> byte(std::string("10010011"); // Set Bit byte.set(3); // 10010111 // Clear Bit byte.reset(2); // 10010101 // Toggle Bit byte.flip(7); // 00010101 cout << byte << endl; return 0; } |
如果您想在Linux内核中使用C编程来执行所有这些操作,那么我建议使用Linux内核的标准API。
请参阅https://www.kernel.org/doc/htmldocs/kernel-api/ch02s03.html
1 2 3 4 5 6 7 | set_bit Atomically set a bit in memory clear_bit Clears a bit in memory change_bit Toggle a bit in memory test_and_set_bit Set a bit and return its old value test_and_clear_bit Clear a bit and return its old value test_and_change_bit Change a bit and return its old value test_bit Determine whether a bit is set |
注意:这里整个操作只需一步。因此,即使在SMP计算机上,这些都是原子的,并且是有用的保持处理器之间的一致性。
VisualC 2010以及其他许多编译器都直接支持内置的位操作。令人惊讶的是,这是可行的,即使sizeof()操作符也能正常工作。
1 2 3 4 5 6 | bool IsGph[256], IsNotGph[256]; // Initialize boolean array to detect printable characters for(i=0; i<sizeof(IsGph); i++) { IsGph[i] = isgraph((unsigned char)i); } |
所以,对于您的问题,isgph[i]=1或isgph[i]=0使设置和清除bools变得容易。
要查找无法打印的字符…
1 2 3 4 5 6 7 8 9 10 | // Initialize boolean array to detect UN-printable characters, // then call function to toggle required bits true, while initializing a 2nd // boolean array as the complement of the 1st. for(i=0; i<sizeof(IsGph); i++) { if(IsGph[i]) { IsNotGph[i] = 0; } else { IsNotGph[i] = 1; } } |
注意,这个代码没有什么"特殊"之处。它处理起来有点像一个整数——从技术上讲,就是这样。一个1位整数,只能保存2个值和2个值。
我曾经使用这种方法查找重复的贷款记录,其中贷款编号是ISAM键,使用6位贷款编号作为位数组的索引。速度惊人,8个月后,证明了我们从中获取数据的主机系统实际上出现了故障。位数组的简单性使得对其正确性的信心非常高,例如与搜索方法相比。
使用此处定义的其中一个运算符。
为了设置一个位,使用
变量使用
1 | int value, pos; |
价值-数据pos—我们感兴趣设置、清除或切换的位的位置。设置一点
1 | value = value | 1 << pos; |
澄清一点
1 | value = value & ~(1 << pos); |
拨动一点
1 | value = value ^ 1 << pos; |
以下是我使用的一些宏:
1 2 3 4 5 6 | SET_FLAG(Status, Flag) ((Status) |= (Flag)) CLEAR_FLAG(Status, Flag) ((Status) &= ~(Flag)) INVALID_FLAGS(ulFlags, ulAllowed) ((ulFlags) & ~(ulAllowed)) TEST_FLAGS(t,ulMask, ulBit) (((t)&(ulMask)) == (ulBit)) IS_FLAG_SET(t,ulMask) TEST_FLAGS(t,ulMask,ulMask) IS_FLAG_CLEAR(t,ulMask) TEST_FLAGS(t,ulMask,0) |
How do you set, clear, and toggle a single bit?
要在试图形成屏蔽时解决常见的编码缺陷:
当
1 2 3 4 5 6 7 8 9 10 | // assume 32 bit int/unsigned unsigned long long number = foo(); unsigned x = 40; number |= (1 << x); // UB number ^= (1 << x); // UB number &= ~(1 << x); // UB x = 10; number &= ~(1 << x); // Wrong mask, not wide enough |
确保1足够宽:
代码可以使用
1 2 | number |= (1ull << x); number |= ((uintmax_t)1 << x); |
或CAST-这使得编码/审查/维护问题保持正确和最新的CAST。
1 | number |= (type_of_number)1 << x; |
或者通过强制执行一个与
1 | number |= (number*0 + 1) << x; |
与大多数位操作一样,最好使用无符号类型,而不是有符号类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | int set_nth_bit(int num, int n){ return (num | 1 << n); } int clear_nth_bit(int num, int n){ return (num & ~( 1 << n)); } int toggle_nth_bit(int num, int n){ return num ^ (1 << n); } int check_nth_bit(int num, int n){ return num & (1 << n); } |
C++ 11模板化版本(放入报头):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | namespace bit { template <typename T1, typename T2> inline void set (T1 &variable, T2 bit) {variable |= ((T1)1 << bit);} template <typename T1, typename T2> inline void clear(T1 &variable, T2 bit) {variable &= ~((T1)1 << bit);} template <typename T1, typename T2> inline void flip (T1 &variable, T2 bit) {variable ^= ((T1)1 << bit);} template <typename T1, typename T2> inline bool test (T1 &variable, T2 bit) {return variable & ((T1)1 << bit);} } namespace bitmask { template <typename T1, typename T2> inline void set (T1 &variable, T2 bits) {variable |= bits;} template <typename T1, typename T2> inline void clear(T1 &variable, T2 bits) {variable &= ~bits;} template <typename T1, typename T2> inline void flip (T1 &variable, T2 bits) {variable ^= bits;} template <typename T1, typename T2> inline bool test_all(T1 &variable, T2 bits) {return ((variable & bits) == bits);} template <typename T1, typename T2> inline bool test_any(T1 &variable, T2 bits) {return variable & bits;} } |
尝试使用C语言中的以下函数之一更改n位:
1 2 3 4 5 6 7 8 | char bitfield; // Start at 0th position void chang_n_bit(int n, int value) { bitfield = (bitfield | (1 << n)) & (~( (1 << n) ^ (value << n) )); } |
或
1 2 3 4 | void chang_n_bit(int n, int value) { bitfield = (bitfield | (1 << n)) & ((value << n) | ((~0) ^ (1 << n))); } |
或
1 2 3 4 5 6 7 8 9 10 11 12 | void chang_n_bit(int n, int value) { if(value) bitfield |= 1 << n; else bitfield &= ~0 ^ (1 << n); } char get_n_bit(int n) { return (bitfield & (1 << n)) ? 1 : 0; } |