6

我们的团队目前正在将一些旧架构的代码移植到基于 ARM Cortex M3 平台的新产品中,该平台使用定制版本的 GCC 4.5.1。我们正在从通信链路读取数据,并尝试将原始字节数组转换为结构以干净地解析数据。在将指针转换为结构并取消引用后,我们收到警告:“取消引用类型双关指针将破坏严格别名规则”。

经过一番研究,我意识到由于 char 数组没有对齐规则并且结构必须是字对齐的,因此转换指针会导致未定义的行为(坏事)。我想知道是否有更好的方法来做我们正在尝试的事情。

我知道我们可以使用 GCC 的“属性((aligned (4)))”明确地对字符数组进行字对齐。我相信这将使我们的代码“更安全”,但警告仍然会使我们的构建变得混乱,并且我不想禁用警告以防这种情况再次出现。我们想要的是一种安全地做我们正在尝试的事情的方法,如果我们稍后在另一个地方尝试做一些不安全的事情,它仍然会通知我们。由于这是一个嵌入式系统,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
}
4

6 回答 6

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;
  }
}

通过这种方式,编译器知道你正在通过不同的方式访问相同的数据,警告和坏事就会消失。

当然,您需要确保结构对齐和打包与您的消息格式相匹配。如果链接另一端的机器不匹配,请注意字节序问题等。

于 2012-01-26T15:22:31.517 回答
4

通过类型转换而不是类型char *或指向有符号/无符号变体的指针的类型双关语char并不严格符合,因为它违反了 C 别名规则(如果不注意,有时还有对齐规则)。

但是,gcc允许通过联合类型进行类型双关。手册页gcc明确记录了它:

从不同的工会成员那里阅读而不是最近写入的成员(称为“类型双关语”)的做法很常见。即使使用 -fstrict-aliasing,也允许使用类型双关语,前提是通过联合类型访问内存。

要禁用与别名规则相关的优化gcc(并因此允许程序破坏 C 别名规则),可以使用以下命令编译程序: -fno-strict-aliasing. 请注意,启用此选项后,程序不再严格符合,但您说可移植性不是问题。有关信息,Linux 内核是使用此选项编译的。

于 2012-01-25T23:32:38.730 回答
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);

将使用您打包的结构来完成。如有必要,您将执行相反的操作以将结构转换回平面缓冲区。

于 2012-01-25T23:23:35.700 回答
2

停止使用打包结构和memcpy单个字段到正确大小和类型的变量中。这是实现您想要实现的目标的安全、便携、干净的方式。如果幸运的话,gcc 会将微小的固定大小优化memcpy为一些简单的加载和存储指令。

于 2012-01-26T02:36:51.627 回答
1

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

于 2012-01-26T00:34:04.803 回答
0

对于未对齐的访问,请查看 linux 宏 get_unaligned/put_unaligned。

于 2012-01-26T15:33:46.690 回答