我们的团队目前正在使用从旧架构到基于ARM Cortex M3平台的新产品的一些移植代码,使用定制版本的GCC 4.5.1。我们正在从通信链接读取数据,并尝试将原始字节数组转换为结构以干净地解析数据。在将指针强制转换为结构并取消引用之后,我们收到一条警告:“取消引用类型惩罚指针将破坏严格别名规则”。
经过一些研究,我意识到由于char数组没有对齐规则,并且结构必须是字对齐的,因此转换指针会导致未定义的行为(糟糕的事情)。我想知道是否有更好的方法来做我们正在尝试的事情。
我知道我们可以使用GCC的“属性((aligned(4)))”明确地对齐char数组。我相信这将使我们的代码“更安全”,但警告仍然会使我们的构建变得混乱,并且我不想在出现这种情况时禁用警告。我们想要的是一种安全地做我们正在尝试的方法,如果我们稍后尝试在另一个地方做一些不安全的事情,它仍会告诉我们。由于这是一个嵌入式系统,RAM的使用和闪存使用在某种程度上很重要。
可移植性(编译器和体系结构)不是一个大问题,这仅适用于一种产品。但是,如果存在便携式解决方案,则首选。
以下是我们目前正在做的一个(非常简化的)示例:
#define MESSAGE_TYPE_A 0
#define MESSAGE_TYPE_B 1
typedef struct MessageA __attribute__((__packed__))
{
unsigned char messageType;
unsigned short data1;
unsigned int data2;
}
typedef struct MessageB __attribute__((__packed__))
{
unsigned char messageType;
unsigned char data3;
unsigned char data4;
}
// This gets filled by the comm system, assume from a UART interrupt or similar
unsigned char data[100];
// Assume this gets called once we receive a full message
void ProcessMessage()
{
MessageA* messageA;
unsigned char messageType = data[0];
if (messageType == MESSAGE_TYPE_A)
{
// Cast data to struct and attempt to read
messageA = (MessageA*)data; // Not safe since data may not be word aligned
// This may cause undefined behavior
if (messageA->data1 == 4) // warning would be here, when we use the data at the pointer
{
// Perform some action...
}
}
// ...
// process different types of messages
}
答案 0 :(得分:6)
正如已经指出的那样,铸造指针是一种狡猾的做法。
解决方案:使用联合
struct message {
unsigned char messageType;
union {
struct {
int data1;
short data2;
} A;
struct {
char data1[5];
int data2;
} B;
} data;
};
void func (...) {
struct message msg;
getMessage (&msg);
switch (msg.messageType) {
case TYPEA:
doStuff (msg.data.A.data1);
break;
case TYPEB:
doOtherStuff (msg.data.B.data1);
break;
}
}
通过这种方式,编译器知道您通过不同方式访问相同的数据,警告和坏事将消失。
对于coure,您需要确保结构对齐和包装符合您的消息格式。如果链接另一端的机器不匹配,请注意字节序问题。
答案 1 :(得分:4)
通过不同于char *
的类型转换或者指向char
的有符号/无符号变体的指针来输入惩罚并不严格符合,因为它违反了C别名规则(如果不小心,有时会对齐规则给出)。
但是,gcc
允许通过联合类型进行类型惩罚。 gcc
的手册页明确记录了它:
从不同的工会成员阅读的做法比最近写的那个(称为“打字式”)很常见。即使 -fstrict-aliasing,允许类型 - 双关语,前提是通过联合类型访问内存。
要使用gcc
禁用与别名规则相关的优化(从而允许程序中断C别名规则),可以使用-fno-strict-aliasing
编译程序。请注意,启用此选项后,程序不再严格符合要求,但您说可移植性不是问题。有关信息,使用此选项编译Linux内核。
答案 2 :(得分:3)
停止使用压缩结构和memcpy
个别字段为正确大小和类型的变量。这是一种安全,便携,干净的方式来完成您想要实现的目标。如果你很幸运,gcc会将微小的固定大小memcpy
优化为一些简单的加载和存储指令。
答案 3 :(得分:2)
GCC有一个-fno-strict-aliasing
标志,可以禁用基于严格别名的优化,并使您的代码安全。
如果您真的在寻找一种“修复”它的方法,那么您必须重新考虑代码的工作方式。您不能只按照您尝试的方式覆盖结构,因此您需要执行以下操作:
MessageA messageA;
messageA.messageType = data[0];
// Watch out - endianness and `sizeof(short)` dependent!
messageA.data1 = (data[1] << 8) + data[2];
// Watch out - endianness and `sizeof(int)` dependent!
messageA.data2 = (data[3] << 24) + (data[4] << 16)
+ (data[5] << 8) + data[6];
此方法可以避免打包您的结构,这也可能会改善代码中其他地方的性能特征。交替:
MessageA messageA;
memcpy(&messageA, data, sizeof messageA);
将使用您的包装结构。如果需要,您可以执行相反的操作将结构转换回平缓冲区。
答案 4 :(得分:1)
Cortex M3可以很好地处理未对齐的访问。我在M3的类似数据包处理系统中完成了这项工作。你不需要做任何事情,只需使用标志-fno-strict-aliasing来消除警告。
答案 5 :(得分:0)
对于未对齐的访问,请查看linux宏get_unaligned / put_unaligned。