为什么位移位在C中要这样写?
Why bit shifting is written like this in C?
也许这个问题有点愚蠢,但我想知道为什么
如果我们想在寄存器中的某个特定位置写 0
我们必须这样写:
PORTB &= ~(1 << 2);
而不是这样(因为它不起作用):
PORTB &= (0 << 2);
这里的 0 是不是像 0b00000000(对于 8 位寄存器)一样?
为了简单起见,使用一个字节,0 << 2
正好等于零 - 0b00000000
。结果,您与此运算的任何值也将产生零。
1 << 2
等于 0b00000100
~(1 << 2)
等于 0b11111011
现在你有了一个 AND 掩码,它只会将特定位设置为零。其余位将保持不变。
让我们一步步分解:
// PORTB &= ~(1 << 2);
int bit_position = 2;
int bit_mask = 1; // = 0000 0001
bit_mask <<= bit_position; // = 0000 0100
int inverted_mask = ~bit_mask; // = 1111 1011
int PORTB = 0x77; // 0111 0111
PORTB &= inverted_mask; // & 1111 1011
// = 0111 0011
而且,您建议的备选方案:
// PORTB &= (0 << 2);
int bit_position = 2;
int bit_mask = 0; // = 0000 0000
bit_mask <<= bit_position; // = 0000 0000
int PORTB = 0x77; // 0111 0111
PORTB &= bit_mask; // & 0000 0000
// = 0000 0000
重点是计算机只是在整个寄存器上一个一个地执行操作。它不知道它应该关心该数字中所有零中的特定零。所以你不是通过“位置 2 处的零”进行 AND 操作,而只是通过数字零进行操作。事实上,您不是只移动一个零或一个,而是移动整个数字。
这就是为什么要将特定位设置为零,我们必须与掩码进行 AND 操作,该掩码在除我们希望为零的位置之外的每个位置都有一个。 (AND 与 1 不改变位,而 AND 与 0 使其为零。)
你反汇编代码就能明白为什么了:
PORTB &= ~(1 << 2);
是这样翻译的:
PORTB = PORTB & (~(0b00000001 << 2));
至
PORTB = PORTB & (~(0b00000100));
至
PORTB = PORTB & ((0b11111011));
这意味着它将采用 PORTB 的旧值并将其第 2 位写入 0,同时将其余位保留为原始值(无论它们是 0 还是 1)
这样做是因为您只想更改 PORTB 的 PIN2 的值而不更改任何其他引脚值。
而另一行:
PORTB &= (0 << 2);
评估为
PORTB = PORTB & ((0b00000000));
这又会将所有 PORTB 引脚重置为 0。
所以这真的取决于你到底想做什么。
再比如你想把PORTB的PIN2设置为1:
PORTB = PORTB | (0b00000100);
可以这样写:
PORTB |= (1 << 2);
这将确保仅将 PIN2 设置为 1,而不会将 PORTB 的其他引脚设置为 1。
也许这个问题有点愚蠢,但我想知道为什么 如果我们想在寄存器中的某个特定位置写 0 我们必须这样写:
PORTB &= ~(1 << 2);
而不是这样(因为它不起作用):
PORTB &= (0 << 2);
这里的 0 是不是像 0b00000000(对于 8 位寄存器)一样?
为了简单起见,使用一个字节,0 << 2
正好等于零 - 0b00000000
。结果,您与此运算的任何值也将产生零。
1 << 2
等于 0b00000100
~(1 << 2)
等于 0b11111011
现在你有了一个 AND 掩码,它只会将特定位设置为零。其余位将保持不变。
让我们一步步分解:
// PORTB &= ~(1 << 2);
int bit_position = 2;
int bit_mask = 1; // = 0000 0001
bit_mask <<= bit_position; // = 0000 0100
int inverted_mask = ~bit_mask; // = 1111 1011
int PORTB = 0x77; // 0111 0111
PORTB &= inverted_mask; // & 1111 1011
// = 0111 0011
而且,您建议的备选方案:
// PORTB &= (0 << 2);
int bit_position = 2;
int bit_mask = 0; // = 0000 0000
bit_mask <<= bit_position; // = 0000 0000
int PORTB = 0x77; // 0111 0111
PORTB &= bit_mask; // & 0000 0000
// = 0000 0000
重点是计算机只是在整个寄存器上一个一个地执行操作。它不知道它应该关心该数字中所有零中的特定零。所以你不是通过“位置 2 处的零”进行 AND 操作,而只是通过数字零进行操作。事实上,您不是只移动一个零或一个,而是移动整个数字。
这就是为什么要将特定位设置为零,我们必须与掩码进行 AND 操作,该掩码在除我们希望为零的位置之外的每个位置都有一个。 (AND 与 1 不改变位,而 AND 与 0 使其为零。)
你反汇编代码就能明白为什么了:
PORTB &= ~(1 << 2);
是这样翻译的:
PORTB = PORTB & (~(0b00000001 << 2));
至
PORTB = PORTB & (~(0b00000100));
至
PORTB = PORTB & ((0b11111011));
这意味着它将采用 PORTB 的旧值并将其第 2 位写入 0,同时将其余位保留为原始值(无论它们是 0 还是 1)
这样做是因为您只想更改 PORTB 的 PIN2 的值而不更改任何其他引脚值。
而另一行:
PORTB &= (0 << 2);
评估为
PORTB = PORTB & ((0b00000000));
这又会将所有 PORTB 引脚重置为 0。
所以这真的取决于你到底想做什么。
再比如你想把PORTB的PIN2设置为1:
PORTB = PORTB | (0b00000100);
可以这样写:
PORTB |= (1 << 2);
这将确保仅将 PIN2 设置为 1,而不会将 PORTB 的其他引脚设置为 1。