假设我有一个结构,无论是联合结构还是其他结构:
typedef struct {
union {
struct { float x, y, z; } xyz;
struct { float r, g, b; } rgb;
float xyz[3];
} notAnonymous;
} Vector3;
我听说有些编译器会自动填充结构,通过创建单词对齐的边界来提高性能。
据推测,这种协同作用意味着结构的大小不能保证是其组件字段大小的总和,因此数组xyzs
的数据损坏和/或溢出的变化如下:
inline Vector3 v3Make(float x, float y, float z) { Vector3 v = {x,y,z}; return v; }
float xyzs[6];
*(Vector3*)&xyzs[3] = v3Make(4.0f,5.0f,6.0f);
*(Vector3*)&xyzs[0] = v3Make(1.0f,2.0f,3.0f);
正确吗?
的确,编译器可以用它想要的任何填充来构建您的结构。您可以使用#pragma pack
或__attribute__((packed))
来避免在大多数编译器上进行填充。在实践中,您有三个32位字段,所以这可能不会成为问题。您可以在结构类型或该类型的变量上使用sizeof
进行检查,并查看结果。
是一个问题是,您试图在最后两行中将Vector3
分配给float
变量。这是不允许的。你可以破解你想做的事情:
*(Vector3 *)&xyzs[3] = v3Make(4.0f, 5.0f, 6.0f);
但这看起来很难看,更不用说令人困惑了。将xyzs
更改为Vector3
的阵列而不仅仅是float
的阵列要好得多。
请参阅C缺陷报告#074 中的问题答案
http://www.open-std.org/jtc1/sc22/wg14/docs/rr/dr_074.html
它本身并不安全,编译器/链接器负责处理所有偏移。
除非。。。将结构传递给用另一种语言或在另一个系统上编写的另一个程序,或者传递给在同一系统上用相同语言编写但具有不同编译器设置的另一程序。那么偏移量可能无法正确计算。
根据C标准,这至少是实现定义的(取决于填充问题),也可能是未定义的行为(由于别名规则?),但在所有真实世界的编译器上,它都能按预期工作。一个类型的对齐永远不能大于它的大小(它总是平均划分类型的大小),只有病态的糟糕编译器才会在结构中插入额外的填充,超过将每个成员填充到其类型的正确对齐所需的填充。
话虽如此,至少在我的书中,这种黑客攻击是为了语法醋而无端调用未定义的行为,是不可接受的。如果你想以数组形式访问数据,只需始终使用数组形式。记住向量分量总是v[0]
、v[1]
和v[2]
比记住v[1]
和rgb.g
可能引用内存中的同一对象要容易得多。。。