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


当前回答

尝试使用C语言中的以下函数之一更改n位:

char bitfield;

// Start at 0th position

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

Or

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

Or

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

其他回答

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

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

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

从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.

换句话说,它只需在指定的位置唯一棘手的部分是我们需要设置的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;
}

----[测试输出(拆卸)]-----------------------------------------------

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

----[完成]-----------------------------------------------------------------

有时值得使用枚举来命名位:

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

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

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

设置、清除和测试。通过这种方式,您可以对代码的其余部分隐藏神奇的数字。

除此之外,我支持佩奇·鲁滕的解决方案。

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

#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测试了它。可以制作类似的宏来设置和清除位。与其他解决方案相比,此解决方案的关键区别在于它适用于几乎任何类型的变量中的任何位置。

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

例子:

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)