在 C 中应用可移植位掩码和标志

Applying portable bit masks and flags in C

我使用各种尺寸的微控制器 architectures/word 为嵌入式系统开发固件,并尽可能强调可移植性。我的问题是关于 generating/applying 安全且可移植的位掩码和标志,对于任何非通用代码,极端情况是什么。

位掩码

#define MASK_8_V1(x) ((x) & 0xFF)
#define MASK_8_V2(x) ((x) & 0x00FF)

a = MASK_8_V1(b)
a = MASK_8_V2(b)

是否总能保证获得 a 的宽度值,其中除 b 的低 8 位外所有位都为零?这两个版本之间有什么区别吗,因为如果需要的话应该对它们进行签名扩展?

旗帜

#define GEN_FLAG_16(x) ((0xFFFF) & ((unsigned) 1 << (x)))
#define GEN_FLAG_32(x) ((0xFFFFFFFF) & ((unsigned long) 1 << (x)))

如果我需要一个通用宏来生成标志常量,这是否总是会生成一个用于列出宽度的标志常量?

两者

#define CHECK_FLAG_16(x, y) ((x) & GEN_FLAG_16(y))
#define CHECK_FLAG_32(x, y) ((x) & GEN_FLAG_32(y))

if(CHECK_FLAG_16(a, b))
{
    // Do something.
}

结合前面的场景,如果设置了b的原始值中的所需位,是否会一直执行内部代码?

对于所有情况,假设:

编辑那些提到使用 stdint.h 的人:我最近遇到一个问题,我们需要将我写过的串行协议处理程序移植到另一个微控制器系列,结果发现 RAM 不是字节可寻址的。我们最终删除了对 uint8_t 的所有使用,并进行了修改以使用 16 位可寻址内存。这让我想知道我是否可以用本机 C 类型以不同方式实现它以避免以后修改。我的问题间接来自那个问题。

您真正应该做的是使用 <stdint.h> 中定义的类型。你确切地知道你的标志需要多少位,所以你选择合适的数据类型。例如:

#define GEN_FLAG_8(x) ((uint8_t)(1 << (x)))
#define GEN_FLAG_16(x) ((uint16_t)(1 << (x)))

您也可以选择相同的保证,但可能使用 uint_fast16_t 等更大的数据类型。

值得一提的是,您还应该制作宏或函数来进行屏蔽,这样您就可以避免在代码中加入整数文字。

Are these always guaranteed to get the value of a's width where all bits are zero'd out except the lower 8 from b?

是的。

宏的结果类型可能会有所不同,具体取决于 b 是什么类型。这可能会导致可移植性问题。因此最好将结果转换为预期的类型,例如 uint32_t.

Is there any difference between the 2 versions

不,它们是等价的。

they should be signed extended if necessary

对有符号类型使用按位运算符通常没有任何意义。

If I need a generic macro for generating flag constants, will this always result in a flag constant for the listed width?

是的,但根据 int 或 long 的大小,结果将具有不同的类型。

I recently came across an issue where we needed to port a serial protocol handler I had written over to another microcontroller family, only to discover that RAM was not byte addressable.

主要是编译器的问题。此外,尚不清楚 uint8_t 在此类系统上如何成为问题,总是存在隐式整数提升。听起来你有一些算法问题,也许代码使用 uint8_t* 指针或类似的东西。


迂腐地,完全可移植的代码看起来像:

#define MASK8_32(x) ((uint32_t)(x) & 0xFFul)

#define GEN_FLAG_16(x) (uint16_t)( 0xFFFFu & (1u << (x)) )
#define GEN_FLAG_32(x) (uint32_t)( 0xFFFFFFFFu & (1ul << (x)) )

现在大部分潜在的整数大小和隐式类型提升依赖已被删除。


此处主要的可移植性问题是:

  • 您的代码依赖于 intlong 的大小这样的代码是不可移植的,因为这些类型可能有任何大小。使用 stdint.h 中的固定宽度整数类型将 解决很多此类问题。
  • 您的代码不知道默认类型和整数文字的符号性。在许多情况下,您对带符号的操作数使用按位运算符,这总是一个坏主意。
  • 您似乎通常没有意识到隐式类型提升在 C 中是如何工作的。

事实证明,所有这些问题都可以通过MISRA-C来解决。我建议购买 MISRA-C:2012 并出于教育目的阅读它。


附带说明,a = OBSUCRE_MACRO(b); 之类的代码 远不如 a = b & 0xFF; 之类的代码可读性 。因为你总是可以假设 reader 是 C 程序员,因此知道 C 语言,但不知道你的私人秘密宏语言。

此外,类似函数的宏是不安全的,应尽可能避免使用。

所以我首先质疑这些宏有什么用。