[英]How to safely perform type-punning in embedded system
我们的团队目前正在使用一些旧版本的移植代码,这些版本的代码使用GCC 4.5.1的自定义版本,基于ARM Cortex M3平台从旧体系结构移植到新产品。 我们正在从通信链接中读取数据,并尝试将原始字节数组转换为结构以干净地解析数据。 将指针强制转换为结构并取消引用后,我们将收到警告:“取消引用类型化指针将破坏严格的别名规则”。
经过一番研究,我意识到由于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
}
正如已经指出的那样,抛弃指针是一种狡猾的做法。
解决方案:使用工会
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;
}
}
通过这种方式,编译器知道您正在通过不同的方式访问相同的数据,并且警告和不良信息将消失。
当然,您需要确保结构对齐和打包与您的消息格式匹配。 请注意字节序问题,如果链接另一端的机器不匹配,则应注意此类问题。
通过键入类型不同于投夯实char *
或指向的符号/无符号的变体char
不严格符合,因为它违反了Ç别名规则(有时走线规则。如果没有护理给出)。
但是, gcc
允许通过联合类型进行类型修剪。 gcc
联机帮助页明确记录了它:
从与最近写过的工会成员不同的工会成员那里进行阅读的做法很常见(称为“类型操纵”)。 即使使用-fstrict-aliasing,只要通过联合类型访问内存,也可以进行类型修剪。
要使用gcc
禁用与别名规则相关的优化(从而允许程序破坏C别名规则),可以使用-fno-strict-aliasing
编译程序。 请注意,启用此选项后,程序不再严格符合要求,但是您说的是可移植性。 有关信息,Linux内核使用此选项进行编译。
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);
将使用您的打包结构来完成。 如果需要,您可以执行反向操作将结构转换回平面缓冲区。
停止使用包装结构和memcpy
各个字段到正确的尺寸和类型的变量。 这是安全,轻便,干净的方法来完成您要实现的目标。 如果幸运的话,gcc会将小型固定大小的memcpy
优化为一些简单的加载和存储指令。
Cortex M3可以很好地处理未对齐的访问。 我已经在与M3类似的数据包处理系统中做到了这一点。 您无需执行任何操作,只需使用标志-fno-strict-aliasing即可消除警告。
对于未对齐的访问,请查看linux宏get_unaligned / put_unaligned。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.