合法写入联合中的字节数组并从int读取以转换MISRA C中的值吗?
不。不得使用工会。
MISRA C:2004,18.4-不得使用工会。
MISRA C:2012,19.2-不应使用union关键字
MISRA C:2004
中的规则遵循:
尽管如此,已经认识到在某些情况下,需要谨慎地使用并集来构造有效的实现。在这种情况下,只要记录了所有相关的实施定义的行为,就可以认为对此规则的偏离是可以接受的。在实践中,这可以通过参考设计文档中编译器手册的“实现”部分来实现。
和
对于(a)打包和拆包数据(例如在发送和接收消息时)和(b)实施变体记录(只要变体由一个公共字段加以区分),可以使用偏差。
您的使用不适合这些情况。
,
尽管从MISRA-C:2004到MISRA-C:2012,此规则已被放宽为Advisory,但正式禁止unions
。禁止union
的主要目的总是防止真正愚蠢的事情,例如在Visual Basic中创建“变量类型”,或通过出于相同目的重新使用同一内存区域。
但是使用union
进行类型修剪是一种常见的做法,尤其是在嵌入式系统中,因此禁止使用它们也很麻烦。规则19.2引起了人们的关注,即写给一个工会成员然后从另一个工会成员那里读取会引起未指定的或实现定义的行为。未指定成员是否不匹配,否则进行定义,因为存在转换。
MISRA对于违反规则的进一步关注是填充,对齐,字节序和位顺序(在位字段的情况下)。这些也是有效的问题-在您的特定示例中,许多都是潜在问题。
我的建议是:
- 仅当您知道自己在做什么时才可以偏离此规则。通过联合修剪的有效类型是寄存器映射声明和序列化/反序列化代码。
-
使用并集来获取某个int的高字节和低字节不是有效的用例,这很糟糕……因为它使代码不必要地不可移植一无所获。假设使用16位系统,绝对没有理由不能用便携式位运算符代替此联合:
int16_t some_int = ...;
uint8_t ms = (uint16_t)some_int >> 8;
uint8_t ls = some_int & 0xFF;
-
通过{伪代码)_Static_assert( sizeof(the_union) == sizeof(all_members)...
确保填充不是问题
- 在带有注释的源代码和MISRA-C实施文档中,记录所有禁用填充的代码。像
#pragma pack(1)
之类的东西,或您的特定编译器使用的任何东西。
,
特别是使用gcc家族(或IAR,GHS,ARM和许多其他编译器)进行联合修剪可以100%罚款。
我知道的所有编译器都遵循脚注95。
如果用于访问并集对象内容的成员不是
与上一次用于在对象中存储值的成员相同,
对象表示的适当部分的值是
重新解释为新类型的对象表示形式,如所述
在6.2.6中(有时称为“类型校正”的过程)。这可能是
陷阱表示。
,
在普通 C中-仅遵循ISO C规则,而不遵循MISRA添加的其他规则-显示的构造为符合,但不是严格符合,因为它取决于未指定的行为。在这种情况下,“未指定”的意思是允许从u16VarNo.IntPart
进行读取为您提供一个根本没有任何意义的值,但是不允许您使程序崩溃,并且编译器也不会允许在永远无法执行读取的前提下进行优化。
确切的规则是C2011 section 6.2.6.1 paragraph 7:
当值存储在联合类型的对象的成员中时,与该成员不对应但与其他成员对应的对象表示形式的字节采用未指定的值。
u16VarNo.BytePart[1]= P1
将值存储在联合类型的对象的成员中。该联合会还有另外两个成员BytePart[0]
和IntPart
¹;它们都覆盖了至少一个与BytePart[1]
不对应的对象表示形式的字节(取决于signed int
的大小);当您写入BytePart[1]
时,该字节取一个未指定的值。
实际的结果是
u16VarNo.BytePart[1] = 0xFF;
u16VarNo.BytePart[0] = 0xFF;
您可以从uint16VarNo.IntPart
中读取内容,但是获得的值很可能是垃圾。特别是
assert(u16VarNo.IntPart == 0xFFFF); // THIS ASSERTION MAY FAIL
我只是模糊地熟悉MISRA的其他规则,但我的印象是,它们完全禁止您执行任何此类操作。
将来自外部源的两个字节数据转换为16位带符号整数的正确方法是使用如下辅助函数:
#include <stdint.h>
int16_t be16_to_cpu_signed(const uint8_t data[static 2])
{
uint32_t val = (((uint32_t)data[0]) << 8) |
(((uint32_t)data[1]) << 0);
return ((int32_t) val) - ((int32_t)0x10000);
}
int16_t le16_to_cpu_signed(const uint8_t data[static 2])
{
uint32_t val = (((uint32_t)data[0]) << 0) |
(((uint32_t)data[1]) << 8);
return ((int32_t) val) - ((int32_t)0x10000);
}
有两个函数,因为您需要知道并在代码中指定外部源在其中提供数据的 endianness 。(这是原始代码无法提供的另一个未关联的原因依赖。)您必须使用32位无符号中间位,因为常数0x10000不适用于16位寄存器。您必须将所有这些显式转换都包含到stdint.h
定宽类型中,因为否则“常规算术转换”很可能为每个步骤选择错误的符号。 (移位和运算必须在无符号算术中完成,最后的减法在有符号算术中。)
¹BytePart[0]
和BytePart[1]
是否是工会的两个独立成员的规定不明确;这是“究竟是什么'对象'”自变量的一个实例,尽管多次尝试修改措辞,但自1989年C标准的最初发布以来一直没有解决。但是,假设编译器不会将它们视为两个单独的对象是不安全的。
本文链接:https://www.f2er.com/2589552.html