如何在嵌入式系统中安全地执行类型惩罚

she*_*les 6 c embedded gcc strict-aliasing type-punning

我们的团队目前正在使用从旧架构到基于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
}
Run Code Online (Sandbox Code Playgroud)

ams*_*ams 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;
  }
}
Run Code Online (Sandbox Code Playgroud)

通过这种方式,编译器知道您通过不同的方式访问相同的数据,警告和坏事将消失.

对于coure,您需要确保结构对齐和打包与您的消息格式匹配.如果链接另一端的机器不匹配,请注意端点问题.


TJD*_*TJD 1

Cortex M3 可以很好地处理未对齐的访问。我已经在使用 M3 的类似数据包处理系统中完成了此操作。您不需要执行任何操作,只需使用标志 -fno-strict-aliasing 即可消除警告。

  • @TLD 废话,我已经发布了解决问题的正确解决方案!看起来不实用吗?抱歉,我在嵌入式系统领域工作了 12 年,我从来没有找到过马虎编程的好借口。该语言为您提供所需的所有功能。根据我的经验,嵌入式开发人员通常对从编译器中获得最后一点效率更感兴趣,而不是更少。如果关闭严格别名,编译器将被迫执行数百或数千次不必要的内存写入,以防两个指针碰巧是别名。然后有人侮辱编译器。 (3认同)
  • -fno-strict-aliasing 关闭非常有用的优化。当然,它可以解决编写糟糕的代码的某些问题,是的,它会使编译器闻起来很糟糕的好代码中令人讨厌的警告消失(就像这样),但实际上,我永远不会建议使用该选项。 (2认同)