提问



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

最佳参考


设置位



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


number |= 1UL << n;


这将设置numbern位。


如果number宽于unsigned long,请使用1ULL; 1UL << n的推广直到评估1UL << n之后才发生,其中未定义的行为偏移超过long的宽度。这同样适用于所有其他示例。


清除一点



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


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


这将清除numbern位。必须使用按位NOT运算符(~)反转位串,然后运行AND。


切换



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


number ^= 1UL << n;


这将切换numbern位。


检查一下



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


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


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


这会将number number的值放入变量bit


将 n 位更改为 x



n位设置为10可以通过以下2的补码C ++实现来实现:


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


如果x1,将设置位n,如果x0,则清除[[]]。如果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);


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


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

其它参考1


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


或Boost版本:boost::dynamic_bitset[125] [126]


没有必要自己动手:


#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


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

其它参考2


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


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


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

其它参考3


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


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

其它参考4


有时值得使用enum到名称这些位:


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


然后使用名称。即写


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


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


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

其它参考5


来自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)))


好吧,让我们分析一下...... [128]


您似乎在所有这些中遇到问题的常见表达是(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.


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


一旦创建了掩码,它就像你建议的那样应用于参数,
通过使用按位和(&),或(|)和xor(^)运算符。自面具
类型为long,宏也可以在chars,shorts,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]] ------------------------------------------- ----------------------

其它参考6


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


例:


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)

其它参考7


使用按位运算符:& |


要设置000b中的最后一位:


foo = foo | 001b


要检查foo中的最后一位:


if ( foo & 001b ) ....


要清除foo中的最后一位:


foo = foo & 110b


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

其它参考8


这是我最喜欢的位算术宏,适用于从unsigned charsize_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, &)) ...


等等

其它参考9


由于这被标记为嵌入式,我将假设您正在使用微控制器。以上所有建议均有效且有效。工作(读 - 修改 - 写,工会,结构等)。


然而,在一次基于示波器的调试中,我惊讶地发现,与直接将值写入微型PORTnSET/PORTnCLEAR寄存器相比,这些方法在CPU周期中具有相当大的开销,这使得存在紧密环路的情况真正发挥作用/高频ISR的切换引脚。


对于那些不熟悉的人:在我的例子中,micro有一个通用的引脚状态寄存器PORTn,它反映了输出引脚,所以做PORTn |=BIT_TO_SET会导致对该寄存器的读 - 修改 - 写。但是,PORTnSET/PORTnCLEAR寄存器取1表示请将该位置1(SET)或请将此位置零(CLEAR),将0表示保持引脚单独。所以,你最终会得到两个端口地址,这取决于你是设置还是清除位(并不总是方便),而是很多更快的反应和更小的汇编代码。

其它参考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,但这可能与实现有关。另外,请验证编译器处理程序如何跨越字节边界。


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

其它参考11


更常见的是,对于任意大小的位图:


#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)))

其它参考12


检查任意类型变量中任意位置的位:



#define bit_test(x, y)  ( ( ((const char*)&(x))[(y)>>3] & 0x80 >> ((y)&0x07)) >> (7-((y)&0x07) ) )


示例用法:


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\n", ix, bit_test(arr, ix));

    return 0;
}


备注:
这是为了快速(具有灵活性)和非分支。在编译Sun Studio 8时,它可以生成高效的SPARC机器代码;我还在amd64上使用MSVC ++ 2008测试了它。它可以制作类似的宏来设置和清除位。与其他许多解决方案相比,此解决方案的主要区别在于它适用于几乎任何类型的变量中的任何位置。

其它参考13


如果你做了很多事情,你可能想要使用掩模,这将使整个事情变得更快。以下功能非常快,并且仍然是灵活的(它们允许在任何大小的位图中进行位杂乱)。


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,请执行以下操作:


TSetBit( n, &my_int);


由您来确保位数在您传递的位图范围内。请注意,对于小端字处理器,字节,字,双字,qword等在内存中正确映射(小端处理器比大端处理器更好的主要原因啊,我觉得火焰大战即将来临......)。

其它参考14


用这个:


int ToggleNthBit ( unsigned char n, int num )
{
    if(num & (1 << n))
        num &= ~(1 << n);
    else
        num |= (1 << n);

    return num;
}

其它参考15


该程序将任何数据位从0更改为1或1更改为0:


{
    unsigned int data = 0x000000F0;
    int bitpos = 4;
    int bitvalue = 1;
    unsigned int bit = data;
    bit = (bit>>bitpos)&0x00000001;
    int invbitvalue = 0x00000001&(~bitvalue);
    printf("%x\n",bit);

    if (bitvalue == 0)
    {
        if (bit == 0)
            printf("%x\n", data);
        else
        {
             data = (data^(invbitvalue<<bitpos));
             printf("%x\n", data);
        }
    }
    else
    {
        if (bit == 1)
            printf("elseif %x\n", data);
        else
        {
            data = (data|(bitvalue<<bitpos));
            printf("else %x\n", data);
        }
    }
}

其它参考16


如果您想在 Linux内核中使用C编程执行所有操作,那么我建议使用Linux内核的标准API。


见https://www.kernel.org/doc/htmldocs/kernel-api/ch02s03.html [129]


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计算机上,所有这些都保证原子并且非常有用
保持处理器之间的一致性。

其它参考17


扩展bitset答案:


#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;
}

其它参考18


Visual C 2010,也许还有许多其他编译器,都直接支持内置的位操作。令人惊讶的是,即使sizeof()运算符也能正常工作。


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使设置和清除bool变得容易。


要找到不可打印的字符......


//  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个值。


我曾经使用这种方法来查找重复的贷款记录,其中loan_number是ISAM密钥,使用6位数的贷款编号作为位数组的索引。野蛮快速,8个月后证明我们从中获取数据的主机系统实际上是故障。位阵列的简单性使得它们的正确性非常高 - 例如,与搜索方法相比。

其它参考19


使用此处定义的运算符之一。[130]


要设置一个位,使用int x = x | 0x?;,其中?是二进制形式的位位置。

其它参考20


这是我使用的一些宏:


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)

其它参考21



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



为了解决在尝试形成掩模时常见的编码缺陷:

1并不总是足够宽


number1更广泛的类型时会发生什么问题?

x对于转移1 << x可能太大了,导致未定义的行为(UB)。即使x不太大,~也可能无法翻转足够多的重要位。


// 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足够宽:


代码可以使用1ull或迂腐(uintmax_t)1并让编译器进行优化。


number |= (1ull << x);
number |= ((uintmax_t)1 << x);


或演员 - 这使得编码/审查/维护问题使演员保持正确和最新。


number |= (type_of_number)1 << x;


或者通过强制进行至少与number类型一样宽的数学运算来轻轻地提升1


number |= (number*0 + 1) << x;





与大多数位操作一样,最好使用 unsigned 类型而不是 signed 类型

其它参考22


一个C ++ 11模板化版本(放在标题中):


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;}
}

其它参考23


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);
}

其它参考24


变量使用


int value, pos;


价值 - 数据

pos - 我们有兴趣设置,清除或切换的位的位置
设置一点


value = value | 1 << pos;


清楚一点


value = value & ~(1 << pos); 


稍微切换


value = value ^ 1 << pos;

其它参考25


用于设置BitIdx - NumberBitValue中的位


Number = Number xor (1 shl BitIdx) or (BitValue shl BitIdx)


这里的诀窍是首先通过将其与1进行xor运而无条件地清除BitIdx位。
这个版本似乎比分支(if bit = 1 then setbit else clearbit略慢,但是是一个单行。

其它参考26


在C语言中尝试使用其中一个函数来改变n位:


char bitfield;

// Start at 0th position

void chang_n_bit(int n, int value)
{
    bitfield = (bitfield | (1 << n)) & (~( (1 << n) ^ (value << n) ));
}


要么


void chang_n_bit(int n, int value)
{
    bitfield = (bitfield | (1 << n)) & ((value << n) | ((~0) ^ (1 << n)));
}


要么


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;
}