当我有一个用户定义的类型时,如下所示:
typedef struct MyData_t {
uint16_t val;
...
} MyData;
还有一个简单的数组,我想用它来存储不同类型的结构:
uint8_t buffer[];
然后我想创建一个结构指针,它使用数组来存储该结构的数据:
MyData* freelist = (MyData*) buffer;
然后我得到MISRA 2012错误:
注意9087:在指向对象类型的指针和指向不同对象类型的指示器之间执行强制转换〔MISRA 2012规则11.3,必需〕
该规则表示,由于可能存在对齐问题,在不同类型的对象之间强制转换指针永远不安全。我的问题是:在嵌入式环境中,编译器在这种情况下引发任何问题的常见程度有多高?当我被迫保留实现概念(关于存储不同类型对象的缓冲区数组)时,我该如何防止这些问题?
如果取消引用freelist
,则调用未定义的行为。两者都是因为可能的对齐问题以及严格的混叠。这是一个错误,MISRA或没有MISRA。最简单的修复方法是使用memcpy
。
在嵌入式环境中,编译器在这种情况下引发任何问题的常见程度如何?
如果对齐,则取决于硬件。一些架构(如MIPS)对对齐非常挑剔,而其他架构(如通用的8-biter MCU)则毫不在意。
至于严格的混叠错误,当gcc编译器在嵌入式系统中开始流行时,在2008-2010年ARM大肆宣传之初的某个地方(gcc版本大约是3.x?),它经常会因为严格的混迭违规而失控。现代的gcc版本具有不那么严格的混叠错误优化。使用gcc时仍然始终使用-fno-strict-aliasing
进行编译,因为编译器不稳定,并且在允许严格混叠的情况下使用通常很危险。
至于常规的嵌入式系统编译器,他们通常不会愚蠢到滥用严格的别名优化,因为他们想出售自己的编译器。
值得注意的是,从结构到字符指针的另一种方法是可以的。MISRA C:2012 11.3然后列出了以下明确的例外情况:
异常
允许将指向对象类型的指针转换为指向对象类型char、有符号char或无符号char之一的指针。
编辑
如果可以打破一些建议性的MISRA规则,比如在整数和指针之间进行强制转换,那么下面的例子可能是一个选择。没有指针转换,没有指针算术,没有严格的混叠问题。您必须将整数强制转换为调用方的结构指针类型,这违反了咨询规则。您必须在链接器脚本中的mempool_addr
地址处留出一个大小为mempool_maxsize
的对齐数据块。
#include <stdint.h>
#include <stddef.h>
#define mempool_maxsize 1024u
#define mempool_addr 0x10000u
static size_t mempool_size=0u;
uintptr_t static_alloc (size_t size)
{
uintptr_t result;
if(mempool_size + size > mempool_maxsize)
{
return 0;
}
if((size % _Alignof(int)) != 0)
{
size += _Alignof(int) - (size % _Alignof(int));
}
result = mempool_addr + mempool_size;
mempool_size += size;
return result;
}
在这种情况下,编译器在嵌入式环境中导致任何问题的常见程度如何?
由于无法满足对齐需求,因此非常常见。例如buffer[]
可能存在于奇数地址上,并且对uint16_t
的访问需要偶数地址。结果:公交车违章。任何铸造都无济于事。
如何防止这些问题
使用uint8_t[]
和struct MyData_t
的union
来对齐并避免混叠问题。
确保uint8_t buffer[]
良好对准的各种方法。示例:
#include <stddef.h>
#define BUF_N 100
union {
uint8_t buffer[BUF_N];
max_align_t a; // Or any wide type like complex long double
} u;
并使用u.buffer
而不是buffer
。
同时研究_Alignas
该规则的基本原理实际上与限制使用union
s的原理相同——不小心会有很多陷阱。
- 如前所述,对齐可能是主要问题
- 严格混叠,同样如前所述
struct
中的填充是另一种
如果你采取适当的步骤(a)确保对齐,(b)确保进出结构的包装/拆包是正确的,(c)确保你没有违反严格的混叠考虑,你可能会逃脱惩罚。
当然,你可以不适用R.18.1,使用myData_t myData
和uint8_t data[]
的union
。。。
但坦率地说,您最好明确地逐个字段地拆包数据。
参见档案了解附属机构
您可以通过首先将错误强制转换为void*
指针来避免错误。
话虽如此,它仍然是由实施定义的行为,因此违反了MISRA的指导方针。只有当您转换为void*
并返回到完全相同的类型时,才能根据标准保证您的有效性。
然而,在嵌入式系统中可能经常存在需要这样做的情况,例如访问特定的内存区域。我至少吃过一些。在这些情况下,您需要根据MISRA由管理层签署此使用。