此代码是否违反 strict-aliasing 规则?
Is this code breaking strict-aliasing rules?
我有一个大缓冲区,表示从 HDD 加载和解压缩的 3D 模型文件,该文件有一个 header 和一些顶点、索引和子集数据。起初我以为我可以计算每个 vertex/index/subset 数据开始的字节偏移量,然后简单地将其转换为兼容的指针类型并使用它,但这会破坏 strict-aliasing 规则。所以一个解决方案是用 memcpy 字节来分隔 vertex/index/subset 个数据数组(每个数组的不同类型的 ofc)?
unsigned char *buf = NULL;
size_t offset = 0;
/* ... */
/* now @buf points to data immediately following the file header */
/* copy mesh subsets list */
memcpy(out->subsets, buf, sizeof(*out->subsets) * header.num_subsets);
/* copy vertex indices list */
offset = sizeof(*out->subsets) * header.num_subsets;
memcpy(out->indices, &buf[offset], sizeof(*out->indices) *
header.num_indices);
/* copy mesh vertices list */
offset += sizeof(*out->indices) * header.num_indices;
memcpy(out->vertices, &buf[offset], sizeof(*out->vertices) *
header.num_vertices);
您从错误的角度攻击严格的别名规则。将 char
的数组投射到您的结构实际上是 UB。这不仅是因为别名,还因为对齐属性可能不同。不要那样做。
你必须反过来做:声明你想要的真实类型的结构,然后使用指向该结构的 void*
或 char*
指针来读取或复制您的数据。
这始终保证有效:
- 字符类型不受严格的别名规则约束
- 将对象的指针传递给函数(
memcpy
或其他)始终确保编译器无法在调用后对该对象的状态做出任何假设,因此他必须重新加载整个对象。
编辑: 也许有些混乱来自关于 "aliasing rules" 的奇怪的 gcc 警告。这只是通过指针转换进行类型双关的问题的一方面。通常通过字符类型以外的错误类型的指针访问对象可能具有未定义的行为。别名只是可能出错的几件事之一。避免它。
将 char*
转换为指向结构的指针的问题不是严格的别名规则:char
类型不受严格的别名规则约束。也就是说,您可以像读取任何其他类型的 char
数据一样读取数据,并且您可以相反地读取任何数据作为 char 数据。
演员表的问题是对齐。除非您直接从内存分配函数中获得了 char*
(保证为任何数据生成一个充分对齐的指针),否则您将面临错位的风险,这可能会使您的程序崩溃。使用 memcpy()
可以解决这个问题。但是,如果您可以确定 char*
完全对齐,则无需复制。
为避免混淆,这是完全合法的代码:
typedef struct Foo {
...
} Foo;
void bar() {
char* buffer = malloc(sizeof(Foo));
fillBuffer(buffer);
Foo* header = (Foo*)buffer; //Ok, buffer is a perfectly aligned pointer.
readHeader(header); //Ok, reading data written as char data does not violate strict aliasing rules.
}
我有一个大缓冲区,表示从 HDD 加载和解压缩的 3D 模型文件,该文件有一个 header 和一些顶点、索引和子集数据。起初我以为我可以计算每个 vertex/index/subset 数据开始的字节偏移量,然后简单地将其转换为兼容的指针类型并使用它,但这会破坏 strict-aliasing 规则。所以一个解决方案是用 memcpy 字节来分隔 vertex/index/subset 个数据数组(每个数组的不同类型的 ofc)?
unsigned char *buf = NULL;
size_t offset = 0;
/* ... */
/* now @buf points to data immediately following the file header */
/* copy mesh subsets list */
memcpy(out->subsets, buf, sizeof(*out->subsets) * header.num_subsets);
/* copy vertex indices list */
offset = sizeof(*out->subsets) * header.num_subsets;
memcpy(out->indices, &buf[offset], sizeof(*out->indices) *
header.num_indices);
/* copy mesh vertices list */
offset += sizeof(*out->indices) * header.num_indices;
memcpy(out->vertices, &buf[offset], sizeof(*out->vertices) *
header.num_vertices);
您从错误的角度攻击严格的别名规则。将 char
的数组投射到您的结构实际上是 UB。这不仅是因为别名,还因为对齐属性可能不同。不要那样做。
你必须反过来做:声明你想要的真实类型的结构,然后使用指向该结构的 void*
或 char*
指针来读取或复制您的数据。
这始终保证有效:
- 字符类型不受严格的别名规则约束
- 将对象的指针传递给函数(
memcpy
或其他)始终确保编译器无法在调用后对该对象的状态做出任何假设,因此他必须重新加载整个对象。
编辑: 也许有些混乱来自关于 "aliasing rules" 的奇怪的 gcc 警告。这只是通过指针转换进行类型双关的问题的一方面。通常通过字符类型以外的错误类型的指针访问对象可能具有未定义的行为。别名只是可能出错的几件事之一。避免它。
将 char*
转换为指向结构的指针的问题不是严格的别名规则:char
类型不受严格的别名规则约束。也就是说,您可以像读取任何其他类型的 char
数据一样读取数据,并且您可以相反地读取任何数据作为 char 数据。
演员表的问题是对齐。除非您直接从内存分配函数中获得了 char*
(保证为任何数据生成一个充分对齐的指针),否则您将面临错位的风险,这可能会使您的程序崩溃。使用 memcpy()
可以解决这个问题。但是,如果您可以确定 char*
完全对齐,则无需复制。
为避免混淆,这是完全合法的代码:
typedef struct Foo {
...
} Foo;
void bar() {
char* buffer = malloc(sizeof(Foo));
fillBuffer(buffer);
Foo* header = (Foo*)buffer; //Ok, buffer is a perfectly aligned pointer.
readHeader(header); //Ok, reading data written as char data does not violate strict aliasing rules.
}