首页 > 解决方案 > 严格的别名和强制转换联合指针

问题描述

我环顾了这个网站,试图弄清楚我对不同工会的使用是否违反了严格的别名或其他 UB。

我有数据包通过串行线路进入,我存储/获取它们,如下所示:

union uart_data {
  struct {
    uint8_t start;
    uint8_t addr;
    uin16_t length;
    uint8_t data[];
  };
  uint8_t bytes[BUFFER_SIZE];
};

void store_byte(uint8_t byte) {
  uart_data->start = byte;
  /* and so on with the other named fields. */
}

uint8_t * get_buffer() {
  return uart_data->bytes;
}

我的理解是,至少对于 GCC 和 GNU 扩展来说,这是一种进行类型双关的有效方法。

但是,然后我想将返回值get_buffer()转换为 uart 不需要知道详细信息的更具体类型的数据包。

union spec_pkt {
  struct {
    uint8_t start;
    uint8_t addr;
    uin16_t length;
    uint8_t command;
    uint8_t some_field;
    uint16_t data_length;
    uint8_t data[];
  };
  uint8_t bytes[BUFFER_SIZE];
};

void process(uint8_t *data) {
  union specific_pkt *pkt = (union specific_pkt *)data;
}

我记得在某处读到这是有效的,因为我正在从联合中存在的类型进行转换,但我找不到源。

我这样做的理由是我可以拥有一个只需要了解最低级别细节的 UART 驱动程序。我在一个 MCU 上,所以我只能访问预先分配的数据缓冲区,这样我就不必memcpy在缓冲区之间浪费空间。在我的应用程序代码中,我可以以比以下方式更好的方式处理数据包:

uint8_t data[BUFFER_SIZE];

data[START_POS];
data[LEN_POS];
data[DATA_POS];

如果这违反了 SA 规则或者是 UB,我希望有一些替代方案来达到同样的效果。

我在支持非对齐访问的目标上使用 GCC,并且 GCC 允许通过联合进行类型双关。

标签: cgcccastingstrict-aliasingtype-punning

解决方案


该标准完全没有指定可以通过类型不是结构或联合的非字符左值访问结构或联合对象的情况。如果一个人认识到标准的目的是纯粹表明编译器何时必须识别出一个看似无关的左值正在访问一个对象,但并不意味着适用于编译器能够看到左值的情况或一种类型的指针用于派生另一种类型,然后用于访问与第一种类型关联的存储,而无需对该存储进行任何干预冲突操作,这种省略是有意义的。例如,给定:

struct sizedPointer { int length,size; int *dat; };
void storeThing(struct sizedPointer *dest, int n)
{
  if (dest->length < dest->size)
  {
    dest->dat[dest->length] = n;
    dest->length++;
  }
}

这样的解释将允许编译器假设dest->length不会使用 编写dest->dat,因为它的值在形成之后已经被观察到dest->dat,但是需要编译器识别给定的:

union blob { uint16_t hh[8]; uint64_t oo[2]; } myBblob;

像这样的操作

sscanf(someString, "%4x", &myBlob.hh[1]);

myBlob可能与函数返回后派生的任何左值交互。

不幸的是,gcc 和 clang 将规则解释为仅在不这样做会完全破坏语言的情况下强制承认。因为标准没有强制要求成员类型的左值可以以任何方式使用,并且 gcc 和 clang 已经明确声明不应该依赖它们来做超出标准要求的任何事情,所以对任何有用的东西的支持应该被视为听任 clang 和 gcc 的维护者的心血来潮。


推荐阅读