我使用各种微控制器架构/字大小为嵌入式系统开发固件,并尽可能地强调可移植性。我的问题是关于生成/应用安全和可移植的位掩码和标志,对于任何非通用代码,角落情况会是什么。
位掩码
#define MASK_8_V1(x) ((x) & 0xFF)
#define MASK_8_V2(x) ((x) & 0x00FF)
a = MASK_8_V1(b)
a = MASK_8_V2(b)
这些是否始终保证得到宽度的值,其中所有位都为零,除了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类型以不同方式实现它以避免以后的修改。我的问题间接来自于这个问题。
答案 0 :(得分:3)
这些是否始终保证得到宽度值,其中除了b中的低8之外所有位都为零?
是。
结果类型的宏可能会有所不同,具体取决于b
的类型。这可能会导致可移植性问题。因此,最好将结果转换为预期类型,例如uint32_t
。
两个版本之间是否存在差异
不,它们是等同的。
如果需要,他们应该签署延期
在签名类型上使用逐位运算符通常没有任何意义。
如果我需要一个通用宏来生成标志常量,那么这总是会导致所列宽度的标志常量吗?
是的,但结果会有不同的类型,具体取决于int或long的大小。
我最近遇到了一个问题,我们需要将我编写的串行协议处理程序移植到另一个微控制器系列,但却发现RAM不是字节可寻址的。
这主要是编译器的问题。此外,尚不清楚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)) )
现在大多数潜在的int-int和隐式类型促销依赖都被删除了。
主要的可移植性问题包括:
int
和long
的大小此类代码是不可移植的,因为这些类型可能有任何大小。使用stdint.h 中的固定宽度整数类型将解决许多这些问题。 事实证明,MISRA-C可以解决所有这些问题。我建议购买MISRA-C:2012并将其用于教育目的。
作为旁注,a = OBSUCRE_MACRO(b);
之类的代码远不如a = b & 0xFF;
这样的代码。因为您总是可以假设读者是C程序员,因此知道C语言,但不知道您的私有秘密宏语言。
此外,类似函数的宏是不安全的,应尽可能避免使用。
所以我首先质疑这些宏的优点。
答案 1 :(得分:-1)
你应该做的是使用<stdint.h>
中定义的类型。您确切知道标志需要多少位,因此您可以选择适当的数据类型。例如:
#define GEN_FLAG_8(x) ((uint8_t)(1 << (x)))
#define GEN_FLAG_16(x) ((uint16_t)(1 << (x)))
您也可以选择相同的保证,但可能选择uint_fast16_t
等更大的数据类型。
值得一提的是,你应该制作宏或函数来进行掩码,这样你就可以避免使用整数文字来编写代码。