【问题标题】:C macro to set multiple bitsC宏设置多个位
【发布时间】:2019-01-25 16:18:11
【问题描述】:

我正在使用 C 语言开发微控制器。其中一部分涉及更改寄存器中的位值。我想出了一些宏来让事情变得更简单:

#define BIT_SET(addr, shift) (addr | (1 << shift))
#define BIT_RESET(addr, shift) (addr & ~(1 << shift))
#define BIT_GET(addr, shift) (addr & (1 << shift))
...
reg1 = BIT_SET(reg1, 3); //set bit 3
...

但是,现在我想制作一个宏,它会一次更改几个位。例如,可能想将10101010 的前3 位更改为110,从而得到数字11001010

我可以在 C 中做到这一点吗?还是我以错误的方式接近这个?

【问题讨论】:

  • 首先想一想,你将如何调用这样的宏?
  • 先是-33,然后是192
  • @cleblanc 这很难读。你能改写/详细说明吗?

标签: c macros c-preprocessor


【解决方案1】:

我的经验是为多个位设置位掩码要容易得多。因此,各个位由特定定义标识,其名称指示位的功能,如

#define ENABLE_T1  0x0001
#define ENABLE_T2  0x0002

然后将它们与按位运算符一起使用来打开和关闭位。

unsigned short usReg1 = GetRegister(REG1);

// disable T1 and enable T2

usReg1 &= ~ ENABLE_T1;
usReg1 |= ENABLE_T2;

// enable both T1 and T2
usReg1 |= (ENABLE_T1 | ENABLE_T2);

// disable both T1 and T2
usReg1 &= ~(ENABLE_T1 | ENABLE_T2);

或测试位

// is T1 turned on
if (usReg1 & ENABLE_T1) {  

// is either T1 or T2 turned on
if (usReg1 & (ENABLE_T1 | ENABLE_T2)) {  

// is T1 turned on and T2 turned off
if ((usReg1 & (ENABLE_T1 | ENABLE_T2)) == ENABLE_T1) {  

// is either T1 or T2 turned on but not both
if ((usReg1 & (ENABLE_T1 | ENABLE_T2)) && ((usReg1 & (ENABLE_T1 | ENABLE_T2)) != (ENABLE_T1 | ENABLE_T2))) {

关于宏的说明

顺便说一句,您需要确保在宏定义中使用括号来尽可能地隔离参数以确保安全。如果不使用括号显式强制将宏参数作为单个表达式进行评估,您将受到运算符优先规则的支配,而这些规则可能并不总是您想要的(参见 https://en.cppreference.com/w/c/language/operator_precedence )。所以

#define BIT_SET(addr, shift) (addr | (1 << shift))

真的应该写成

#define BIT_SET(addr, shift) ((addr) | (1 << (shift)))

对于您的示例,由于按位移位运算符在优先级图表上相当低,因此在大多数情况下可能会很好,但对于其他宏,取决于运算符优先级可能会导致缺陷,因为您永远不知道某人会多么保守使用宏时。

【讨论】:

  • 我同意;这提供了更大的灵活性并提高了可读性。 (不过,我个人会将宏(或常量)定义为(1 &lt;&lt; 0)(1 &lt;&lt; 1)(1 &lt;&lt; 2) 等。)
  • @jamesdlin 我认为宏直接映射到文档功能而不是通用位数。大多数使用位的文档都指定了一些八进制或十六进制或十进制数字(通常是三个),它们提供了用于打开/关闭某些功能或指示某些状态的位掩码值。
【解决方案2】:

您可以引入一个全角掩码来定义您要设置的位。 例如。设置前 3 位使用 0xe0,即二进制 11100000。

与该掩码一起,提供要全角写入的值。
例如。 0xc0 表示前三位中所需的二进制 110。

然后需要一点操作魔法来只写入所需的位位置。

#define MULTIBIT_SET(addr, mask, value) (((addr)&((0xff)^(mask)))|((value)&(mask)))

这里((0xff)^(mask)) 反转掩码,因此与此操作会从值中删除现有位。
((value)&amp;(mask)) 是值,但仅在所需位置设置位,即这样可以防止不希望的位设置其他地方。

总的来说,oring 两个部分,可以得到你想要的结果。

这里有一个设计选择,我想提一下:
称之为偏执狂。如果有人告诉我“我只想将前三位设置为这个在其他地方有位的值”,那么我更愿意以这种方式安全,即删除“其他位”。
是的,我假设无意设置位比不设置位风险更大,这是一个品味问题。

【讨论】:

  • @MadPhysicist 您似乎不再看到问题了。但是,我已经听取了您的意见,以使我的答案更清楚。谢谢。
【解决方案3】:

你总是可以在 C 中做到这一点。:-)

如果您使用的是符合 C99 的编译器,并且考虑到现在是 2019 年,我假设您是,您可以使用可变参数宏来使其工作,更具体地说,P99

我有一个示例,我提取了 P99 的相关组件,但它变得一团糟,因为这是对 C 预处理器作为图灵完备语言的一些认真使用。

这也滥用了括号;特别是,EXTRACT_VALUE x 扩展为 EXTRACT_VALUE (a, b),因为我们传递了一个元组列表。

#include "p99.h"

#define EXTRACT_SHIFT(shift, value) (shift)
#define EXTRACT_VALUE(shift, value) (value)
#define MERGE(NAME, I, A, B) (A), (B)

#define SET_OR_CLEAR(target, x, i) \
    ((EXTRACT_VALUE x) ? \
        ((target) |= (1 << EXTRACT_SHIFT x)) : \
        ((target) &= ~(1 << EXTRACT_SHIFT x)))
#define SET_AND_CLEAR(target, pairs...) \
    P99_FOR(target, P99_NARG(pairs), MERGE, SET_OR_CLEAR, pairs)

// ...
reg1 = BIT_SET(reg3, 3); //set bit 3                                         
// ...
SET_AND_CLEAR(reg3, (7, 1), (6, 1), (5, 0)); // set bits 6 and 7, clear bit 5

您可能需要使用-std=c99 进行编译。请注意,我使用了可变参数词 pairs 而不是 __VA_ARGS__,以表明我们想要 2 元组作为参数。

请注意,这不会屏蔽任何未设置或清除的输入寄存器。

【讨论】:

  • 我从这里的另一个问题中发现了它!很高兴能帮上忙。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-10-31
相关资源
最近更新 更多