C 设置位(位操作)
C set a bit (bit manipulation)
我正在为工业 plc 编程,我必须操作位以与 VFD 进行 profi-bus 通信。我得到一个 2byte 状态并且必须发送 2byte 命令。对于此操作,我必须设置位以使 VFD 运行。例如:
Byte n+1 Byte n
PLC --> --------------------- --------------- --> VFD
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
---------+--------- | | | | -+- | | +- 0: Reglersperre / Freigabe
| | | | | | | +--- 1: Freigabe / Schnellstopp
| | | | | | +----- 2: Freigabe / Halt
| | | | | +-------- 3..4: reserviert = 0
| | | | +------------5: Parametersatz-Umschaltung
| | | +------------- 6: Reset
| | +--------------- 7: reserviert = 0
| |
| +----------------- 8: Lüften der Bremse ohne Antreibsfreigabe
+---------------------------- 9..15: reserviert = 0
所以我必须设置位 0 以将 VFD 设置为运行模式。然后我需要设置位 2 来启动驱动器。
现在我找到了一个 question,其中描述了位处理,我发现这个解决方案应该可行,但我不太明白。
有人可以解释为什么这有效或无效吗?
uint16_t change_bit(uint16_t command, unsigned int bit_nr, unsigned int val) {
/* command = 2byte command; bit_nr = bit to manipulate;
val = value bit should get (1;0) */
command ^= (-val ^ command) & (1U << bit_nr);
return command;
}
这似乎可行,但它非常令人惊讶并且不太清楚。有人可以 "too clever"。更清晰的方法可能是:
uint16_t change_bit(uint16_t command, unsigned int bit, bool value)
{
const uint16_t mask = 1u << bit;
if(value)
return command | mask;
else
return command & ~mask;
}
其中有一个跳转(if
),但聪明的编译器可能会优化它。如果它不是对性能非常关键的代码,那么清晰度通常会更好。
请注意,在进行位级操作时,使用无符号类型通常是个好主意。
这确实是一个小修改而不分支的小技巧。这是假设您了解按位运算符如何工作的解释。
让我们从重新排列表达式开始
(-val ^ command) & (1 << bit_nr)
首先,让我们交换 -val
和 command
(command ^ -val) & (1 << bit_nr)
然后,应用分配律
(command & (1 << bit_nr)) ^ (-val & (1 << bit_nr))
现在,实现(假设二进制补码)如果 val
为 0,则 -val
为 0(未设置位),如果 val
为 1,则 -val
为 -1(设置所有位)
(command & (1 << bit_nr)) ^ (val ? (1 << bit_nr) : 0)
因此可以将赋值重写为 if 语句
if (val)
command ^= (command & (1 << bit_nr)) ^ (1 << bit_nr);
else
command ^= command & (1 << bit_nr);
如果 val
为 1,则位置 bit_nr
的位与其取反的值进行异或运算,该值始终设置该位。如果 val
为 0,则该位与自身进行异或运算,始终清除该位。所有其他位与 0 进行异或,使它们保持不变。
这是一个更具可读性的无分支版本,它将按位运算换成了移位:
uint16_t change_bit(uint16_t command, unsigned int bit_nr, unsigned int val) {
// Always clear the bit.
command &= ~(1u << bit_nr);
// Set the bit if val == 1.
command |= val << bit_nr;
return command;
}
我正在为工业 plc 编程,我必须操作位以与 VFD 进行 profi-bus 通信。我得到一个 2byte 状态并且必须发送 2byte 命令。对于此操作,我必须设置位以使 VFD 运行。例如:
Byte n+1 Byte n
PLC --> --------------------- --------------- --> VFD
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
---------+--------- | | | | -+- | | +- 0: Reglersperre / Freigabe
| | | | | | | +--- 1: Freigabe / Schnellstopp
| | | | | | +----- 2: Freigabe / Halt
| | | | | +-------- 3..4: reserviert = 0
| | | | +------------5: Parametersatz-Umschaltung
| | | +------------- 6: Reset
| | +--------------- 7: reserviert = 0
| |
| +----------------- 8: Lüften der Bremse ohne Antreibsfreigabe
+---------------------------- 9..15: reserviert = 0
所以我必须设置位 0 以将 VFD 设置为运行模式。然后我需要设置位 2 来启动驱动器。
现在我找到了一个 question,其中描述了位处理,我发现这个解决方案应该可行,但我不太明白。
有人可以解释为什么这有效或无效吗?
uint16_t change_bit(uint16_t command, unsigned int bit_nr, unsigned int val) {
/* command = 2byte command; bit_nr = bit to manipulate;
val = value bit should get (1;0) */
command ^= (-val ^ command) & (1U << bit_nr);
return command;
}
这似乎可行,但它非常令人惊讶并且不太清楚。有人可以 "too clever"。更清晰的方法可能是:
uint16_t change_bit(uint16_t command, unsigned int bit, bool value)
{
const uint16_t mask = 1u << bit;
if(value)
return command | mask;
else
return command & ~mask;
}
其中有一个跳转(if
),但聪明的编译器可能会优化它。如果它不是对性能非常关键的代码,那么清晰度通常会更好。
请注意,在进行位级操作时,使用无符号类型通常是个好主意。
这确实是一个小修改而不分支的小技巧。这是假设您了解按位运算符如何工作的解释。
让我们从重新排列表达式开始
(-val ^ command) & (1 << bit_nr)
首先,让我们交换 -val
和 command
(command ^ -val) & (1 << bit_nr)
然后,应用分配律
(command & (1 << bit_nr)) ^ (-val & (1 << bit_nr))
现在,实现(假设二进制补码)如果 val
为 0,则 -val
为 0(未设置位),如果 val
为 1,则 -val
为 -1(设置所有位)
(command & (1 << bit_nr)) ^ (val ? (1 << bit_nr) : 0)
因此可以将赋值重写为 if 语句
if (val)
command ^= (command & (1 << bit_nr)) ^ (1 << bit_nr);
else
command ^= command & (1 << bit_nr);
如果 val
为 1,则位置 bit_nr
的位与其取反的值进行异或运算,该值始终设置该位。如果 val
为 0,则该位与自身进行异或运算,始终清除该位。所有其他位与 0 进行异或,使它们保持不变。
这是一个更具可读性的无分支版本,它将按位运算换成了移位:
uint16_t change_bit(uint16_t command, unsigned int bit_nr, unsigned int val) {
// Always clear the bit.
command &= ~(1u << bit_nr);
// Set the bit if val == 1.
command |= val << bit_nr;
return command;
}