严格别名和转换联合指针
Strict aliasing and casting union pointers
我浏览了这个站点,试图弄清楚我对不同联合的转换是否违反了严格的别名或其他 UB。
我有数据包通过串行线路传入,我 store/get 它们像:
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 扩展,这是一种进行类型双关的有效方法。
但是,然后我想将 return 值从 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
,浪费 space。在我的应用程序代码中,我可以用比以下更好的方式处理数据包:
uint8_t data[BUFFER_SIZE];
data[START_POS];
data[LEN_POS];
data[DATA_POS];
如果这违反了 SA 规则或者是 UB,我希望有一些替代方案来实现相同的目标。
我在支持未对齐访问的目标上使用 GCC,并且 GCC 允许通过联合进行类型双关。
标准完全没有指定在什么情况下可以通过类型不是结构或联合类型的非字符左值访问结构或联合对象。如果人们认识到该标准的目的纯粹是指示编译器何时必须认识到一个对象正在被 看似无关的 左值访问,但并不意味着适用于以下情况编译器将能够看到一种类型的左值或指针用于派生另一种类型,然后用于访问与第一个类型相关联的存储,而无需对该存储进行任何干预冲突操作,这种遗漏是有道理的。例如,给定:
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]);
可能与函数 returns.
之后从 myBlob
派生的任何左值交互
不幸的是,gcc 和 clang 将规则解释为仅在不这样做会完全破坏语言的情况下强制识别。因为标准不强制要求成员类型左值可以任何方式使用,并且 gcc 和 clang 明确声明不应该依赖它们来做超出标准要求的任何事情,所以对任何有用的支持都应该被视为是 clang 和 gcc 维护者的心血来潮。
我浏览了这个站点,试图弄清楚我对不同联合的转换是否违反了严格的别名或其他 UB。
我有数据包通过串行线路传入,我 store/get 它们像:
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 扩展,这是一种进行类型双关的有效方法。
但是,然后我想将 return 值从 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
,浪费 space。在我的应用程序代码中,我可以用比以下更好的方式处理数据包:
uint8_t data[BUFFER_SIZE];
data[START_POS];
data[LEN_POS];
data[DATA_POS];
如果这违反了 SA 规则或者是 UB,我希望有一些替代方案来实现相同的目标。
我在支持未对齐访问的目标上使用 GCC,并且 GCC 允许通过联合进行类型双关。
标准完全没有指定在什么情况下可以通过类型不是结构或联合类型的非字符左值访问结构或联合对象。如果人们认识到该标准的目的纯粹是指示编译器何时必须认识到一个对象正在被 看似无关的 左值访问,但并不意味着适用于以下情况编译器将能够看到一种类型的左值或指针用于派生另一种类型,然后用于访问与第一个类型相关联的存储,而无需对该存储进行任何干预冲突操作,这种遗漏是有道理的。例如,给定:
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]);
可能与函数 returns.
之后从myBlob
派生的任何左值交互
不幸的是,gcc 和 clang 将规则解释为仅在不这样做会完全破坏语言的情况下强制识别。因为标准不强制要求成员类型左值可以任何方式使用,并且 gcc 和 clang 明确声明不应该依赖它们来做超出标准要求的任何事情,所以对任何有用的支持都应该被视为是 clang 和 gcc 维护者的心血来潮。