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

How to set, clear, and toggle a single bit

提问人:JeffV 提问时间:9/7/2008 最后编辑:Peter MortensenJeffV 更新时间:11/3/2023 访问量:1622882

问:

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

C++ C 操作 按位运算符

评论

90赞 ugasoft 9/19/2008
阅读此内容:graphics.stanford.edu/~seander/bithacks.html,当您掌握此内容时,请阅读此内容:realtimecollisiondetection.net/blog/?p=78
22赞 1/5/2009
您可能还有兴趣查看 The Bit TwiddlerBit Twiddling HacksThe Aggregate Magic Algorithms
2赞 Peter Mortensen 8/29/2022
相关新闻: 什么是按位移位(位移)运算符,它们是如何工作的?
2赞 Peter Mortensen 9/13/2022
一些候选者:如何使用 C 替换位域中的位而不影响其他位(2011 年),以及如何在 C 中仅设置字节的某些位而不影响其余位?(2010 年)

答:

4333赞 Paige Ruten 9/7/2008 #1

设置位

使用按位 OR 运算符 () 将 的第 1 位设置为 。|nnumber1

// Can be whatever unsigned integer type you want, but
// it's important to use the same type everywhere to avoid
// performance issues caused by mixing integer types.
typedef unsigned long Uint;

// In C++, this can be template.
// In C11, you can make it generic with _Generic, or with macros prior to C11.
inline Uint bit_set(Uint number, Uint n) {
    return number | ((Uint)1 << n);
}

请注意,移动超过 .这同样适用于所有剩余的示例。Uint

稍微清除一下

使用按位 AND 运算符 () 将 的第 1 位设置为 。&nnumber0

inline Uint bit_clear(Uint number, Uint n) {
    return number & ~((Uint)1 << n);
}

您必须使用按位 NOT 运算符 () 反转位字符串,然后使用 AND it。~

切换一点

使用按位异或运算符 () 切换 的第 1 位。^nnumber

inline Uint bit_toggle(Uint number, Uint n) {
    return number ^ ((Uint)1 << n);
}

检查一下

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

要检查一点,请向右移动,然后按位移动:numbern

// bool requires #include <stdbool.h> prior to C23
inline bool bit_check(Uint number, Uint n) {
    return (number >> n) & (Uint)1;
}

将第 n位更改为 x

还有其他方法的 codegen 较差,但最好的方法是清除 中的位,然后将位设置为值,类似于 。bit_clearbit_set

inline Uint bit_set_to(Uint number, Uint n, bool x) {
    return (number & ~((Uint)1 << n)) | ((Uint)x << n);
}

所有解决方案都经过测试,可通过 GCC 和 clang 提供最佳的代码生成。请参见 https://godbolt.org/z/Wfzh8xsjW

评论

163赞 Aaron 9/18/2008
我想指出的是,在原生支持位集/清除的平台上(例如,AVR 微控制器),编译器通常会将“myByte |= (1 << x)”转换为原生位集/清除指令,只要 x 是常量,例如:(1 << 5) 或 const unsigned x = 5。
55赞 Chris Young 11/16/2008
位 = 数字 & (1 << x);除非位的类型为 _Bool (<stdbool.h>),否则不会将位 x 的值放入位中。否则,bit = !!(数字 & (1 << x));将。。
24赞 aaronman 6/27/2013
你为什么不把最后一个改成bit = (number >> x) & 1
49赞 Siyuan Ren 12/10/2013
1是带有符号的文字。因此,这里的所有操作都使用有符号数字进行操作,而标准没有很好地定义这些数字。该标准不保证 2 的补码或算术移位,因此最好使用 .int1U
71赞 leoly 3/24/2015
我更喜欢将第 n 位更改为 x。number = number & ~(1 << n) | (x << n);
143赞 dmckee --- ex-moderator kitten 9/9/2008 #2

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

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

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

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

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

除此之外,我赞同佩奇·鲁滕(Paige Ruten)的解决方案

评论

2赞 endolith 12/20/2011
或者,您可以创建一个函数而不是 .你为什么要为此使用枚举?我以为这些是用来创建一堆具有隐藏任意值的唯一变量,但你为每个变量分配了一个确定的值。那么,与仅仅将它们定义为变量相比有什么好处呢?clearbits()&= ~
5赞 dmckee --- ex-moderator kitten 12/22/2011
@endolith:在 c 编程中,使用 s 表示相关常量集可以追溯到很久以前。我怀疑现代编译器的唯一优势是它们被明确地组合在一起。当您希望它们用于位掩码以外的其他内容时,您会得到自动编号。当然,在 c++ 中,它们也形成了不同的类型,这为您提供了一些额外的静态错误检查。enumconst short
1赞 Luis Colorado 9/30/2014
如果不为位的每个可能值定义一个常量,则将进入未定义的枚举常量。例如,有什么价值?enum ThingFlagsThingError|ThingFlag1
8赞 Lundin 12/14/2015
如果使用此方法,请记住枚举常量始终是 signed 类型。这可能会导致各种细微的 bug,因为隐式整数升级或对有符号类型进行按位运算。 例如,将调用实现定义的行为。 可以调用未定义的行为。等等。为了安全起见,请始终强制转换为无符号类型。intthingstate = ThingFlag1 >> 1thingstate = (ThingFlag1 >> x) << y
3赞 Aiken Drum 3/15/2016
@Lundin:从 C++11 开始,您可以设置枚举的基础类型,例如:enum My16Bits: unsigned short { ... };
285赞 Ferruccio 9/11/2008 #3

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

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

struct bits mybits;

定义一个 3 位字段(实际上,它是三个 1 位felds)。位操作现在变得更简单了(哈哈):

要设置或清除位:

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

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

评论

82赞 R.. GitHub STOP HELPING ICE 6/28/2010
我一直觉得使用位域是个坏主意。您无法控制分配位的顺序(从顶部或底部开始),这使得无法以稳定/可移植的方式序列化值,除非一次位。也不可能将 DIY 位算术与位场混合在一起,例如制作一个一次测试多个位的掩码。你当然可以使用&&,并希望编译器能够正确地优化它......
45赞 Lundin 8/19/2011
位字段在很多方面都很糟糕,我几乎可以写一本关于它的书。事实上,对于一个需要 MISRA-C 合规性的现场计划,我几乎不得不这样做。MISRA-C 强制要求记录所有实现定义的行为,因此我最终写了一篇关于位字段中可能出错的所有内容的文章。位顺序、字节序、填充位、填充字节、各种其他对齐问题、与位字段之间的隐式和显式类型转换、UB(如果未使用 int)等。相反,请使用按位运算符来减少错误和可移植代码。位字段是完全冗余的。
52赞 Ferruccio 8/19/2011
与大多数语言功能一样,位字段可以正确使用,也可以被滥用。如果需要将几个小值打包到单个 int 中,则位字段非常有用。另一方面,如果你开始假设位字段如何映射到实际包含的 int,你只是在自找麻烦。
5赞 Ferruccio 3/9/2012
@endolith:那可不是一个好主意。你可以让它工作,但它不一定可以移植到不同的处理器,或者不同的编译器,甚至不能移植到同一个编译器的下一个版本。
4赞 Kelly S. French 12/9/2016
@Yasky 和 Ferruccio 对这种方法的 sizeof() 给出了不同的答案,这应该说明了不仅在编译器之间,而且在硬件之间都存在兼容性问题。我们有时会自欺欺人地认为我们已经用语言或定义的运行时解决了这些问题,但实际上归结为“它能在我的机器上工作吗?你们这些嵌入式家伙得到了我的尊重(和同情)。
58赞 yogeesh 9/17/2008 #4

来自 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因此由此产生的拆卸将与 可能:

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

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

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

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

评论

3赞 R.. GitHub STOP HELPING ICE 6/28/2010
不要将表用于可以使用单个运算符实现的函数。TQuickByteMask[n] 等效于 (1<<n)。此外,让你的论点简短是一个非常糟糕的主意。/ 和 % 实际上是一个除法,而不是位移/按位除法,因为有符号除以 2 的幂不能按位实现。您应该将参数类型设置为无符号 int!
1赞 Lundin 8/19/2011
这有什么意义?它只会使代码更慢、更难阅读?我看不出它有什么优势。对于C程序员来说,1u << n 更容易阅读,并且有望转换为单个时钟滴答的 CPU 指令。另一方面,你的除法将被转换为大约 10 个刻度,甚至高达 100 个刻度,这取决于特定架构处理除法的程度。至于位图功能,使用查找表将每个位索引转换为字节索引以优化速度会更有意义。
2赞 Lundin 8/19/2011
至于 big/little endian,big endian 将以相同的方式映射整数和原始数据(例如字符串):在整个位图中从左到右将 msb 映射到 lsb。虽然 little endian 会将整数从左到右映射为 7-0、15-8、23-18、31-24,但原始数据仍然是从左到右的 msb 到 lsb。因此,对于您的特定算法来说,端序有多小是完全超出我的范围,似乎恰恰相反。
3赞 jeb 11/18/2011
@R..如果您的平台不能像旧的微芯片 MCU 那样有效地移动,那么工作台可能会很有用,但当然,样品中的分割绝对是低效的
557赞 Martin York 9/18/2008 #6

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

或者 Boost 版本:boost::d ynamic_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;
}

./a.out

输出:

00010

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

评论

43赞 paercebal 9/20/2008
+1.并不是说 std::bitset 可以从“C”使用,但正如作者用“C++”标记他/她的问题一样,AFAIK,你的答案是这里最好的......std::vector<bool> 是另一种方式,如果知道它的优点和缺点
29赞 Niklas 12/13/2008
@andrewdotnich:vector<bool>(不幸的是)是一种将值存储为位的专用化。有关更多信息,请参阅 gotw.ca/publications/mill09.htm...
92赞 Lundin 8/19/2011
也许没有人提到它,因为这被标记为嵌入式。在大多数嵌入式系统中,您可以像避免瘟疫一样避免 STL。在大多数嵌入式编译器中,boost 支持可能是一种非常罕见的鸟类。
21赞 Lundin 8/19/2011
@Martin 这是千真万确的。除了 STL 和模板等特定的性能杀手之外,许多嵌入式系统甚至完全避免使用整个标准库,因为它们验证起来非常痛苦。大多数嵌入式分支都在接受像MISRA这样的标准,这需要静态代码分析工具(顺便说一句,任何软件专业人员都应该使用这样的工具,而不仅仅是嵌入式人员)。一般来说,人们有更好的事情要做,而不是通过整个标准库运行静态分析——如果它的源代码甚至可以在特定的编译器上获得。
47赞 Martin York 8/19/2011
@Lundin:你的陈述过于宽泛(因此争论毫无用处)。我敢肯定,如果情况是真的,我能找到。这并没有改变我最初的观点。这两个类都非常适合在嵌入式系统中使用(我知道它们被使用的事实)。你最初关于STL/Boost不能在嵌入式系统上使用的观点也是错误的。我敢肯定,有些系统不使用它们,甚至使用它们的系统,它们也被明智地使用,但说它们不被使用是不正确的(因为有些系统被使用了)。
251赞 6 revs, 5 users 64%Steve Karg #7

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

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

评论

20赞 Robert Kelly 10/2/2013
呃,我意识到这是一个 5 年前的帖子,但任何这些宏中都没有重复的论点,Dan
15赞 brigadir 12/11/2014
BITMASK_CHECK(x,y) ((x) & (y))否则,它会在多位掩码上返回不正确的结果(例如。 vs. ) /*向所有掘墓人问好 :)*/((x) & (y)) == (y)53
9赞 M.M 2/7/2015
1应该或类似,以防有人试图在或更大的类型上使用这些宏(uintmax_t)1long
4赞 Handy999 11/20/2018
BITMASK_CHECK_ALL(x,y)可以实现为!~((~(y))|(x))
5赞 Tavian Barnes 8/14/2019
@Handy999 在应用德摩根定律并重新安排得到!(~(x) & (y))
27赞 Roddy 11/6/2008 #8

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

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,但这可能取决于实现。此外,请验证编译器如何处理跨越字节边界的字段。

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

评论

4赞 Lundin 8/19/2011
关于位域的几乎所有内容都是由实现定义的。即使你设法找出关于你的特定编译器如何实现它们的所有细节,在你的代码中使用它们肯定会使它变得不可移植。
1赞 Roddy 8/20/2011
@Lundin - 没错,但嵌入式系统位摆弄(特别是在硬件寄存器中,这是我的答案所涉及的)无论如何都不会有用地移植。
1赞 Lundin 8/20/2011
也许不是在完全不同的 CPU 之间。但你很可能希望它在编译器之间和不同项目之间是可移植的。而且还有很多与硬件完全无关的嵌入式“位摆弄”,比如数据协议编码/解码。
0赞 2/16/2013
...如果你养成了使用位字段进行嵌入式编程的习惯,你会发现你的 X86 代码运行得更快、更精简。不是在简单的基准测试中,你有整台机器来粉碎基准测试,而是在程序争夺资源的现实世界的多任务环境中。优势 CISC - 其最初的设计目标是弥补 CPU 比总线快和内存慢的问题。
0赞 Peter Mortensen 11/3/2023
C 还是 C++?语言版本(如果有)有什么要求?
22赞 John Zwinck 1/4/2009 #9

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

#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赞 thangavel 4/12/2009 #10

使用这个:

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

    return num;
}

评论

7赞 asdf 7/3/2011
好吧,它使用了低效的分支。
5赞 M.M 2/7/2015
@asdf编译器的工作是输出最高效的二进制文件,程序员的工作是编写清晰的代码
4赞 Ben Voigt 2/22/2015
这是测试、设置和清除特定位的良好演示。但是,这是一种非常糟糕的切换方法。
22赞 bill 6/14/2009 #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)))

评论

4赞 M.M 2/7/2015
CHAR_BIT已经由 定义了,你不需要输入你自己的代码(事实上,这样做会让你的代码变得更糟)limits.hBITS
30赞 R.. GitHub STOP HELPING ICE 7/13/2010 #12

这是我最喜欢的位算术宏,它适用于任何类型的无符号整数数组,从最多到(这是应该有效使用的最大类型):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, &)) ...

等。

评论

5赞 foraidt 7/13/2010
阅读很好,但应该注意可能的副作用。在循环中使用很可能不会执行调用方想要的操作。BITOP(array, bit++, |=);
0赞 R.. GitHub STOP HELPING ICE 7/13/2010
事实上。=) 您可能更喜欢的一种变体是将其分成 2 个宏,一个用于寻址数组元素,另一个用于将位移动到位,ala (两者都作为确定大小的参数,但后者永远不会计算,因为它只出现在 )。BITCELL(a,b) |= BITMASK(a,b);aasizeof
1赞 PC Luddite 10/24/2015
@R..这个答案真的很老,但在这种情况下,我可能更喜欢函数而不是宏。
0赞 chux - Reinstate Monica 9/28/2017
次要:第 3 个演员似乎只是为了确保一些无符号的数学。可以在那里。(size_t)%(unsigned)
0赞 chux - Reinstate Monica 9/28/2017
在分裂之前,不必要的范围可能会缩小。只有非常大的位数组有问题。仍然是一个有趣的宏。(size_t)(b)/(8*sizeof *(a))b
17赞 Gokul Naathan 2/29/2012 #13

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

评论

0赞 Peter Mortensen 8/22/2023
它不是一个程序,只是一个片段。
0赞 Peter Mortensen 8/22/2023
什么是一些示例输出?
0赞 Peter Mortensen 8/22/2023
好的,OP 已经离开了大楼:“最后一次出现是在 11 年前”
0赞 Peter Mortensen 8/22/2023
它看起来像一个虚假的答案,在问题发布 3 1/2 年后发布。它有什么优点吗?
44赞 kapilddit 6/5/2012 #14

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

例:

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)
30赞 John U 6/14/2012 #15

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

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

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

评论

0赞 John U 6/20/2012
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/......
1赞 Ben Voigt 2/22/2015
更重要的是,辅助内存映射的 I/O 寄存器提供了一种原子更新机制。如果序列中断,读取/修改/写入可能会非常糟糕。
3赞 Lundin 12/14/2015
请记住,所有端口寄存器都将被定义为,因此编译器无法对涉及此类寄存器的代码执行任何优化。因此,最好拆解此类代码,看看它在汇编程序级别上的结果如何。volatile
0赞 Peter Mortensen 8/22/2023
目前尚不清楚。什么更快?直接在内存映射的 I/O 上运行?在(大概)CPU 寄存器中执行所有操作并将最终结果写入 I/O 寄存器?
0赞 Peter Mortensen 8/22/2023
直接在I/O寄存器上操作会导致输出引脚上出现毛刺(脉冲),其中按位运算的中间结果被写出。
12赞 user1899861 12/30/2012 #16

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 位整数,可以保存两个值,并且只能保存两个值。

我曾经使用这种方法来查找重复的贷款记录,其中loan_number是 ISAM 密钥,使用六位数的贷款编号作为位数组的索引。 它的速度非常快,八个月后,证明我们从中获取数据的大型机系统实际上出现了故障。位数组的简单性使其对正确性的信心非常高,例如,与搜索方法相比。

评论

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

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

评论

1赞 M.M 2/7/2015
value << n可能导致未定义的行为
0赞 4/18/2021
更改为或避免UB @M.M正在谈论10x11UL
4赞 sam msft 2/7/2015 #19

以下是我使用的一些宏:

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)
13赞 Jeegar Patel 5/28/2016 #20

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

请参阅第 2 章。C 库基本函数

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

6赞 chux - Reinstate Monica 9/28/2017 #21

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

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

当类型比 更宽时会发生什么问题?
对于导致未定义行为 (UB) 的转变来说可能太大了。即使不是太大,也可能没有翻转足够多的最有效位。
number1x1 << xx~

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

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

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

与大多数位操作一样,最好使用符号类型而不是有符号类型。

评论

0赞 chqrlie 9/28/2017
一个有趣的老问题!既不适合设置有符号类型的符号位......事实上,两者都不是.有没有一种便携式方法可以按位置进行?number |= (type_of_number)1 << x;number |= (number*0 + 1) << x;number |= (1ull << x);
1赞 chux - Reinstate Monica 9/28/2017
@chqrlie IMO 中,避免设置符号位并冒着 UB 或 IDB 移位风险的最佳方法是使用无符号类型。高度可移植的移位符号代码太复杂了,无法接受。
1赞 ad absurdum 9/13/2022
太可惜了,找到这个好的答案需要这么多的滚动!
4赞 Joakim L. Christiansen 2/11/2018 #22

支持更改多个位的模板化版本(放在头文件中)(顺便说一句,适用于 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 输出。

评论

3赞 pqnet 10/26/2019
在 C++11 中使用std::bitset
8赞 Sazzad Hissain Khan 2/21/2018 #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);
}

评论

1赞 Xeverous 5/6/2020
的返回类型可以是 。check_nth_bitbool
1赞 Sazzad Hissain Khan 5/6/2020
@Xeverous是的,这取决于来电者的意图
30赞 Pankaj Prakash 6/10/2019 #24

让我们先假设几件事:

num = 55:用于执行按位运算(set、get、clear 和 toggle)的整数。

n = 4:从 0 开始的位位置,用于执行按位运算。

我们怎样才能得到一点?

要获得数字,右移位,时间。然后用 1 按位执行 AND。nthnumn&

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 次。然后使用 执行按位 OR 运算。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,次,即 .n1 << n
  2. 对上述结果进行按位补码。因此,第 n 位变为未设置,其余位变为已设置,即 .~ (1 << n)
  3. 最后,对上述结果和 执行按位 AND 运算。以上三个步骤一起可以写成&numnum & (~ (1 << 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 运算符。如果两个操作数的相应位不同,则按位 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)

评论

0赞 Chandra Shekhar 12/29/2019
感谢您的详细解释。这是BIT Magic链接的练习题链接
5赞 Balaji Boggaram Ramanarayan 4/23/2020 #25

该程序基于 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);
    }
}

输出:

8
0
0
true

评论

0赞 Peter Mortensen 8/22/2023
没有人叫“杰里米”。它指的是什么答案?
-1赞 Dominic van der Zypen 2/6/2021 #26

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

有时,当您不确定 -1 或类似结果时,您可能希望在不使用 -1 的情况下设置第 n 位:

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

解释:将第 n 位设置为 1(其中表示按位 OR),然后我们将第 n 位设置为 0,最后将第 n 位设置为 0,设置为(位值)。((number | (1 << n)|(...) ^ (1 << n)(...) | x << n)x

这在 Go 中也有效。

评论

2赞 Will Eccles 5/8/2021
这可能更简洁(并且可能更有效,除非编译器优化您的解决方案),因为 .(number & ~(1 << n)) | (!!x << n)
3赞 lckid2004 3/17/2021 #27

下面是 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;   
}