【问题标题】:How do I set, clear, and toggle a single bit?如何设置、清除和切换单个位?
【发布时间】:2023-02-14 20:26:03
【问题描述】:

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

【问题讨论】:

标签: c++ c bit-manipulation bitwise-operators


【解决方案1】:

设置一点

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

number |= 1UL << n;

这将设置 numbernth 位。 n 应该为零,如果你想设置1st 位等等直到n-1,如果你想设置nth 位。

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

清一点

使用按位与运算符 (&amp;) 清除位。

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

这将清除 numbernth 位。您必须使用按位 NOT 运算符 (~) 反转位串,然后将其与。

切换一下

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

number ^= 1UL << n;

这将切换 numbernth 位。

检查一下

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

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

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

这会将numbernth位的值放入变量bit中。

改变n第一点到X

nth 位设置为 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);

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

或者

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

(number &amp; ~(1UL &lt;&lt; n)) 将清除nth 位,(x &lt;&lt; n)nth 位设置为x

一般来说,不要复制/粘贴代码也是一个好主意,所以很多人使用预处理器宏(如the community wiki answer further down)或某种封装。

【讨论】:

  • 我想指出,在本机支持位设置/清除(例如,AVR 微控制器)的平台上,编译器通常会在 x 为时将“myByte |= (1 << x)”转换为本机位设置/清除指令常数,例如:(1 << 5),或 const unsigned x = 5。
  • 位 = 数字 & (1 << x);不会将位 x 的值放入位,除非位的类型为 _Bool (<stdbool.h>)。否则,bit = !!(number & (1 << x));将要..
  • 为什么不把最后一个改成bit = (number &gt;&gt; x) &amp; 1
  • 1 是一个 int 字面值,已签名。所以这里的所有操作都对有符号数进行操作,标准没有很好地定义它。该标准不保证补码或算术移位,因此最好使用1U
  • 我更喜欢 number = number &amp; ~(1 &lt;&lt; n) | (x &lt;&lt; n); 将第 n 位更改为 x。
【解决方案2】:

使用标准 C++ 库:std::bitset&lt;N&gt;

或者 Boost 版本: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

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

【讨论】:

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

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

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 强制记录所有实现定义的行为,因此我最终写了一篇关于位字段中可能出错的所有内容的文章。位顺序、字节序、填充位、填充字节、各种其他对齐问题、与位字段之间的隐式和显式类型转换、UB(如果不使用 int)等等。相反,使用位运算符来减少错误和可移植代码。位域是完全冗余的。
  • 与大多数语言特性一样,位域可以被正确使用,也可以被滥用。如果您需要将几个小值打包到一个 int 中,位域可能非常有用。另一方面,如果您开始假设位字段如何映射到实际包含的 int,那么您只是在自找麻烦。
  • @endolith:那不是一个好主意。你可以让它工作,但它不一定可以移植到不同的处理器、不同的编译器甚至同一编译器的下一个版本。
  • @Yasky 和 ​​Ferruccio 对这种方法的 sizeof() 得到不同的答案应该说明兼容性问题不仅在编译器之间而且在硬件之间。我们有时会自欺欺人,我们已经用语言或定义的运行时解决了这些问题,但实际上归结为“它能在我的机器上运行吗?”。你们嵌入式的家伙有我的尊重(和同情)。
【解决方案4】:

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

/* 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) &amp; (y)) 必须是 ((x) &amp; (y)) == (y) 否则它会在多位掩码上返回不正确的结果(例如 53)/*所有掘墓人你好:)*/
  • 1 应该是 (uintmax_t)1 或类似的,以防有人试图在 long 或更大的类型上使用这些宏
  • BITMASK_CHECK_ALL(x,y)可以实现为!~((~(y))|(x))
  • @Handy999 在应用 De Morgan 定律并重新安排得到 !(~(x) &amp; (y)) 之后,更容易理解为什么它会起作用
【解决方案5】:

有时值得使用 enum姓名位:

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

然后使用名字稍后的。 IE。写

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

设置,清除和测试。通过这种方式,您可以从其余代码中隐藏幻数。

除此之外,我支持Paige Ruten's solution

【讨论】:

  • 或者,您可以创建一个 clearbits() 函数而不是 &amp;= ~。为什么要为此使用枚举?我认为那些是为了创建一堆具有隐藏的任意值的唯一变量,但你正在为每个变量分配一个确定的值。那么与仅仅将它们定义为变量相比有什么好处呢?
  • @endolith:enums 用于相关常量集的使用在 C 编程中可以追溯到很久以前。我怀疑对于现代编译器而言,与const short 或其他任何东西相比,唯一的优势是它们被明确地组合在一起。当你想要他们做某事时其他比位掩码你得到自动编号。当然,在 C++ 中,它们也形成不同的类型,这为您提供了一些额外的静态错误检查。
  • 如果您不为每个可能的位值定义常量,您将进入未定义的枚举常量。例如,ThingError|ThingFlag1enum ThingFlags 值是多少?
  • 如果您使用此方法,请记住枚举常量始终是有符号类型 int。由于对有符号类型进行隐式整数提升或按位运算,这可能会导致各种细微的错误。 thingstate = ThingFlag1 &gt;&gt; 1 例如将调用实现定义的行为。 thingstate = (ThingFlag1 &gt;&gt; x) &lt;&lt; y 可以调用未定义的行为。等等。为了安全起见,请始终转换为无符号类型。
  • @Lundin:从 C++11 开始,您可以设置枚举的基础类型,例如:enum My16Bits: unsigned short { ... };
【解决方案6】:

来自 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, 或做多。

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

不服气?这是一些测试代码——我使用了完全优化的 Watcom C 并且不使用 _cdecl 所以最终的反汇编会像 可能的:

----[ 测试.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	st.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

----[结束]---------------------------------------- ----------------------

【讨论】:

  • 关于此的 2 件事:(1) 在仔细阅读您的宏时,有些人可能错误地认为宏实际上在 arg 中设置/清除/翻转位,但是没有赋值; (2) 你的test.c不完整;我怀疑如果你运行更多的案例你会发现问题(读者练习)
  • -1 这只是奇怪的混淆。永远不要通过隐藏宏背后的语言语法来重新发明 C 语言,它是非常不好的做法。然后是一些奇怪的事情:首先,1L 是有符号的,这意味着所有位操作都将在有符号类型上执行。传递给这些宏的所有内容都将以带符号的形式返回。不好。其次,这在较小的 CPU 上工作效率非常低,因为当操作本可以在 int 级别时它会强制执行很长时间。第三,类函数宏是万恶之源:你没有任何类型安全。另外,之前关于没有分配的评论是非常有效的。
  • 如果arglong 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】:

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

例子:

value is 0x55;
bitnum : 3rd.

&amp; 运算符用于检查位:

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】:

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

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

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

    【讨论】:

    • Micro 是 Coldfire MCF52259,在 Codewarrior 中使用 C。查看反汇编程序/asm 是一项有用的练习,因为它显示了 CPU 必须执行的所有步骤,即使是最基本的操作也是如此。 <br>我们还在时间关键循环中发现了其他占用 CPU 的指令 - 通过执行 var %= max_val 来约束变量每次都会花费大量 CPU 周期,而执行 if(var > max_val)var-=max_val 仅使用一些说明。 <br>关于更多技巧的好指南在这里:codeproject.com/Articles/6154/…
    • 更重要的是,辅助内存映射 I/O 寄存器提供了一种原子更新机制。如果序列被中断,读取/修改/写入可能会非常糟糕。
    • 请记住,所有端口寄存器都将定义为volatile,因此编译器无法对涉及此类寄存器的代码执行任何优化。因此,反汇编此类代码并查看其在汇编程序级别的结果是一种很好的做法。
    【解决方案9】:

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

    ETC。

    【讨论】:

    • 读起来很好,但应该注意可能的副作用。在循环中使用 BITOP(array, bit++, |=); 很可能不会做调用者想要的。
    • 的确。 =) 您可能更喜欢的一种变体是将它分成 2 个宏,一个用于寻址数组元素,另一个用于将位移动到位,ala BITCELL(a,b) |= BITMASK(a,b);(两者都将 a 作为参数来确定大小,但是后者永远不会评估 a,因为它只出现在 sizeof 中)。
    • @R ..这个答案真的很老,但在这种情况下我可能更喜欢函数而不是宏。
    • 次要:第三个(size_t)演员似乎只是为了确保一些无符号数学%。可以 (unsigned) 那里。
    • (size_t)(b)/(8*sizeof *(a)) 在除法之前可能会不必要地缩小 b。只有非常大的位数组才有问题。仍然是一个有趣的宏。
    【解决方案10】:

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

    如何获得一点?

    1. 要获取 num 的 nth 位右移 num, n 次。然后用 1 执行按位与 &amp;
      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. 设置数字的特定位。左移 1 n 次。然后对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. 左移 1,n 次,即 1 &lt;&lt; n
        2. 对上述结果进行位补。这样第 n 位变为未设置,其余位变为设置,即 ~ (1 &lt;&lt; n)
        3. 最后,将上述结果与num进行按位与&amp;运算。以上三步合起来可以写成num &amp; (~ (1 &lt;&lt; n))

          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进行异或运算。

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

          怎么运行的?

          • 如果要切换的位为 0,则 0 ^ 1 =&gt; 1
          • 如果要切换的位为 1,则 1 ^ 1 =&gt; 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
    【解决方案11】:

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

    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 之间。但是您很可能希望它可以在编译器之间和不同项目之间移植。并且有很多与硬件无关的嵌入式“位摆弄”,例如数据协议编码/解码。
    • ...如果您养成了使用位域进行嵌入式编程的习惯,您会发现您的 X86 代码运行得更快,也更精简。不是在简单的基准测试中,您需要整台机器来粉碎基准测试,而是在程序竞争资源的真实多任务环境中。 Advantage CISC - 其最初的设计目标是弥补 CPU 比总线快和内存慢的缺点。
    【解决方案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
    ", ix, bit_test(arr, ix));
    
        return 0;
    }
    

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

    【讨论】:

      【解决方案13】:

      更一般的,对于任意大小的位图:

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

      【讨论】:

      • CHAR_BIT 已经由limits.h 定义,你不需要输入你自己的BITS(事实上你这样做会让你的代码变得更糟)
      【解决方案14】:

      该程序是将任何数据位从 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
      ",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);
              }
          }
      }
      

      【讨论】:

        【解决方案15】:

        如果你正在做很多小事,你可能想使用面具,这将使整个事情更快。以下函数速度非常快,而且仍然很灵活(它们允许在任何大小的位图中进行位旋转)。

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

        由您来确保位数在您传递的位图的范围内。请注意,对于字节、字、双字、四字等在内存中相互正确映射的小端处理器(小端处理器比大端处理器“更好”的主要原因,啊,我觉得一场激烈的战争即将来临在...)。

        【讨论】:

        • 不要将表用于可以通过单个运算符实现的功能。 TQuickByteMask[n] 等价于 (1<<n)。此外,让你的论点简短是一个非常糟糕的主意。 / 和 % 实际上是一个除法,而不是位移位/按位和,因为不能按位实现按 2 的幂的有符号除法。您应该将参数类型设为 unsigned int!
        • 这有什么意义呢?它只会让代码变慢并且更难阅读?我看不出它有任何优势。 1u << n 对于 C 程序员来说更容易阅读,并且有望被翻译成单个时钟滴答 CPU 指令。另一方面,您的除法将被转换为大约 10 个滴答,甚至高达 100 个滴答,这取决于特定架构处理除法的糟糕程度。至于位图功能,使用查找表将每个位索引转换为字节索引以优化速度会更有意义。
        • 至于大/小端,大端将以相同的方式映射整数和原始数据(例如字符串):在整个位图中从左到右的msb到lsb。虽然小端会将整数从左到右映射为 7-0、15-8、23-18、31-24,但原始数据仍然是从左到右的 msb 到 lsb。因此,对于您的特定算法,小端字节序到底有多好,这完全超出了我的范围,似乎恰恰相反。
        • @R ..如果你的平台不能像旧的微芯片单片机那样有效地移动,那么表格可能很有用,但当然样本中的划分是绝对低效的
        【解决方案16】:

        用这个:

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

        【讨论】:

        • 好吧,它使用低效的分支。
        • @asdf 编译器的工作是输出最高效的二进制文件,程序员的工作是编写清晰的代码
        • 这是测试、设置和清除特定位的一个很好的演示。然而,这是一种非常糟糕的切换方式。
        【解决方案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】:

          如果你想用 C 编程在内核那么我建议使用 Linux 内核的标准 API。

          https://www.kernel.org/doc/htmldocs/kernel-api/ch02s03.html

          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 计算机上也很有用 保持处理器之间的一致性。

          【讨论】:

            【解决方案19】:

            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] =1IsGph[i] =0 可以轻松设置和清除布尔值。

            要查找不可打印的字符:

            //  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 个月后,证明我们从中获取数据的大型机系统实际上出现了故障。位数组的简单性使人们对其正确性的信心非常高——与搜索方法相比。

            【讨论】:

            • 大多数编译器确实将 std::bitset 实现为位
            • @galinette,同意。头文件#include <bitset> 是这方面的一个很好的资源。此外,特殊类 vector<bool> 用于当您需要更改向量大小时。 C++ STL,第 2 版,Nicolai M. Josuttis 分别在第 650 和 281 页详尽地介绍了它们。 C++11 为 std::bitset 添加了一些新功能,我特别感兴趣的是无序容器中的哈希函数。感谢您的提醒!我要删除我脑抽筋的评论。网络上的垃圾已经够多了。我不想添加它。
            • 这对每个bool 至少使用一个完整字节的存储空间。对于使用 int 实现 bool 的 C89 设置,甚至可能需要 4 个字节
            • @MattMcNabb,你是对的。在 C++ 中,标准未指定实现布尔值所需的 int 类型的大小。不久前我意识到这个答案是错误的,但决定把它留在这里,因为人们显然发现它很有用。对于那些想使用 bits 的人,galinette 的评论和我的 bit 库一样最有帮助... stackoverflow.com/a/16534995/1899861
            • @RocketRoy:可能值得更改声称这是“位操作”示例的句子。
            【解决方案20】:
            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);
            }
            

            【讨论】:

            • check_nth_bit的返回类型可以是bool
            • @Xeverous 是的,这取决于呼叫者的意图
            【解决方案21】:

            这是我使用的一些宏:

            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)
            

            【讨论】:

              【解决方案22】:

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

              要解决尝试形成掩码时的常见编码陷阱:
              1 并不总是足够宽

              number 是比1 更宽的类型时会发生什么问题?
              x 对于转变 1 &lt;&lt; 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;
              

              与大多数位操作一样,最好使用未签名类型而不是那些

              【讨论】:

              • 有趣的看一个老问题! number |= (type_of_number)1 &lt;&lt; x;number |= (number*0 + 1) &lt;&lt; x; 都不适合设置有符号类型的符号位...事实上,number |= (1ull &lt;&lt; x); 也不合适。有没有一种可移植的方式来按位置进行操作?
              • @chqrlie IMO,避免设置符号位和冒 UB 或 IDB 风险的最佳方法是使用未签名类型。高度便携的移位代码太复杂,无法接受。
              • 太糟糕了,找到这个好的答案需要滚动很多次!
              【解决方案23】:

              该程序基于@Jeremy 的上述解决方案。如果有人想快点玩。

              public class BitwiseOperations {
              
                  public static void main(String args[]) {
              
                      setABit(0, 4); // set the 4th bit, 0000 -> 1000 [8]
                      clearABit(16, 5); // clear the 5th bit, 10000 -> 00000 [0]
                      toggleABit(8, 4); // toggle the 4th bit, 1000 -> 0000 [0]
                      checkABit(8,4); // check the 4th bit 1000 -> true 
                  }
              
                  public static void setABit(int input, int n) {
                      input = input | ( 1 << n-1);
                      System.out.println(input);
                  }
              
              
                  public static void clearABit(int input, int n) {
                      input = input & ~(1 << n-1);
                      System.out.println(input);
                  }
              
                  public static void toggleABit(int input, int n) {
                      input = input ^ (1 << n-1);
                      System.out.println(input);
                  }
              
                  public static void checkABit(int input, int n) {
                      boolean isSet = ((input >> n-1) & 1) == 1; 
                      System.out.println(isSet);
                  }
              }
              
              
              Output :
              8
              0
              0
              true
              

              【讨论】:

                【解决方案24】:

                支持更改多个位的模板化版本(放在头文件中)(顺便说一句,适用于 AVR 微控制器):

                namespace bit {
                  template <typename T1, typename T2>
                  constexpr inline T1 bitmask(T2 bit) 
                  {return (T1)1 << bit;}
                  template <typename T1, typename T3, typename ...T2>
                  constexpr inline T1 bitmask(T3 bit, T2 ...bits) 
                  {return ((T1)1 << bit) | bitmask<T1>(bits...);}
                
                  /** Set these bits (others retain their state) */
                  template <typename T1, typename ...T2>
                  constexpr inline void set (T1 &variable, T2 ...bits) 
                  {variable |= bitmask<T1>(bits...);}
                  /** Set only these bits (others will be cleared) */
                  template <typename T1, typename ...T2>
                  constexpr inline void setOnly (T1 &variable, T2 ...bits) 
                  {variable = bitmask<T1>(bits...);}
                  /** Clear these bits (others retain their state) */
                  template <typename T1, typename ...T2>
                  constexpr inline void clear (T1 &variable, T2 ...bits) 
                  {variable &= ~bitmask<T1>(bits...);}
                  /** Flip these bits (others retain their state) */
                  template <typename T1, typename ...T2>
                  constexpr inline void flip (T1 &variable, T2 ...bits) 
                  {variable ^= bitmask<T1>(bits...);}
                  /** Check if any of these bits are set */
                  template <typename T1, typename ...T2>
                  constexpr inline bool isAnySet(const T1 &variable, T2 ...bits) 
                  {return variable & bitmask<T1>(bits...);}
                  /** Check if all these bits are set */
                  template <typename T1, typename ...T2>
                  constexpr inline bool isSet (const T1 &variable, T2 ...bits) 
                  {return ((variable & bitmask<T1>(bits...)) == bitmask<T1>(bits...));}
                  /** Check if all these bits are not set */
                  template <typename T1, typename ...T2>
                  constexpr inline bool isNotSet (const T1 &variable, T2 ...bits) 
                  {return ((variable & bitmask<T1>(bits...)) != bitmask<T1>(bits...));}
                }
                

                使用示例:

                #include <iostream>
                #include <bitset> // for console output of binary values
                
                // and include the code above of course
                
                using namespace std;
                
                int main() {
                  uint8_t v = 0b1111'1100;
                  bit::set(v, 0);
                  cout << bitset<8>(v) << endl;
                
                  bit::clear(v, 0,1);
                  cout << bitset<8>(v) << endl;
                
                  bit::flip(v, 0,1);
                  cout << bitset<8>(v) << endl;
                
                  bit::clear(v, 0,1,2,3,4,5,6,7);
                  cout << bitset<8>(v) << endl;
                
                  bit::flip(v, 0,7);
                  cout << bitset<8>(v) << endl;
                }
                

                顺便说一句:事实证明,如果不向编译器发送优化器参数(例如:-O3),则不会使用 constexpr 和 inline。请随意尝试https://godbolt.org/ 的代码并查看 ASM 输出。

                【讨论】:

                • 此代码已损坏。 (另外,为什么你的函数定义后有 ;?)
                • @melpomene 代码没有损坏,我确实测试过了。你的意思是它不会编译或者结果是错误的?关于额外的';'我不记得了,那些确实可以删除。
                • (variable &amp; bits == bits)
                • 感谢您的关注,应该是((variable &amp; bits) == bits)
                • 在 c++11 中使用std::bitset
                【解决方案25】:

                下面是 C 中执行基本位运算的例程:

                #define INT_BIT (unsigned int) (sizeof(unsigned int) * 8U) //number of bits in unsigned int
                
                int main(void)
                {
                    
                    unsigned int k = 5; //k is the bit position; here it is the 5th bit from the LSb (0th bit)
                    
                    unsigned int regA = 0x00007C7C; //we perform bitwise operations on regA
                    
                    regA |= (1U << k);    //Set kth bit
                    
                    regA &= ~(1U << k);   //Clear kth bit
                    
                    regA ^= (1U << k);    //Toggle kth bit
                    
                    regA = (regA << k) | regA >> (INT_BIT - k); //Rotate left by k bits
                    
                    regA = (regA >> k) | regA << (INT_BIT - k); //Rotate right by k bits
                
                    return 0;   
                }
                
                

                【讨论】:

                  【解决方案26】:

                  在不使用 -1 的情况下将第 n 位设置为 x(位值)

                  有时当您不确定 -1 或类似的结果会导致什么时,您可能希望设置第 n 位而不使用 -1:

                  number = (((number | (1 << n)) ^ (1 << n))) | (x << n);
                  

                  解释:((number | (1 &lt;&lt; n) 将第 n 位设置为 1(其中 | 表示按位或),然后使用 (...) ^ (1 &lt;&lt; n) 将第 n 位设置为 0,最后使用 (...) | x &lt;&lt; n) 将第 n 位设置为 0, (位值)x

                  这也适用于golang

                  【讨论】:

                  • 这可能更简洁(并且可能更有效,除非编译器优化您的解决方案)如(number &amp; ~(1 &lt;&lt; n)) | (!!x &lt;&lt; n)
                  【解决方案27】:

                  尝试使用 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;
                  }
                  

                  【讨论】:

                  • value &lt;&lt; n 可能会导致未定义的行为
                  • 1 更改为 0x11UL 以避免 UB @M.M 正在谈论
                  猜你喜欢
                  • 2010-09-08
                  • 2010-11-29
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多