让我们有一个简单的结构(POD)。
struct xyz
{
float x, y, z;
};
我可以假设以下代码可以吗?我可以假设没有任何差距吗?标准是怎么说的?POD 是这样吗?上课是这样吗?
xyz v;
float* p = &v.x;
p[0] = 1.0f;
p[1] = 2.0f; // Is it ok?
p[2] = 3.0f; // Is it ok?
标准不保证这一点,并且不适用于许多系统。原因是:
这意味着它p[1]
可能与 位于同一位置xyz.y
,或者它可能部分重叠,或者根本不重叠。
不,除了第一个字段之外,这样做是不行的。
从 C++ 标准:
9.2 类成员
指向 POD 结构对象的指针,使用 reinterpret_cast 适当转换,指向其初始成员(或者如果该成员是位字段,则指向它所在的单元),反之亦然。[注意:因此,在 POD 结构对象中可能存在未命名的填充,但不是在其开头,这是实现适当对齐所必需的。
取决于硬件。该标准明确允许 POD 类具有未指定和不可预测的填充。我在 C++ Wikipedia 页面上注意到了这一点,并为您抓取了带有规范参考的脚注。
^ ab ISO/IEC (2003)。ISO/IEC 14882:2003(E):编程语言 - C++ §9.2 类成员 [class.mem] 段。17
然而,实际上,在常见的硬件和编译器上它会很好。
如有疑问,请更改数据结构以适应应用程序:
struct xyz
{
float p[3];
};
为了便于阅读,您可能需要考虑:
struct xyz
{
enum { x_index = 0, y_index, z_index, MAX_FLOATS};
float p[MAX_FLOATS];
float X(void) const {return p[x_index];}
float X(const float& new_x) {p[x_index] = new_x;}
float Y(void) const {return p[y_index];}
float Y(const float& new_y) {p[y_index] = new_y;}
float Z(void) const {return p[z_index];}
float Z(const float& new_z) {p[z_index] = new_z;}
};
甚至可能添加更多封装:
struct Functor
{
virtual void operator()(const float& f) = 0;
};
struct xyz
{
void for_each(Functor& ftor)
{
ftor(p[0]);
ftor(p[1]);
ftor(p[2]);
return;
}
private:
float p[3];
}
一般来说,如果需要以两种或多种不同的方式处理数据结构,则可能需要重新设计数据结构;或代码。
该标准要求内存中的排列顺序与定义的顺序相匹配,但允许在它们之间进行任意填充。如果您在成员之间有访问说明符(public:
,private:
或protected:
),则即使是关于顺序的保证也会丢失。
编辑:在所有三个成员都属于相同原始类型(即它们本身不是结构或类似的东西)的特定情况下,您有相当大的机会——对于原始类型,对象的大小和对齐要求通常是相同的,所以它成功了。
OTOH,这只是偶然,而且往往更像是弱点而不是优势。代码是错误的,所以理想情况下它会立即失败而不是看起来工作,直到您为将成为您最重要客户的公司所有者进行演示的那一天,届时它将(当然)以最令人发指的方式失败......
不,您可能不会假设没有差距。您可以检查您的架构,如果没有并且您不关心可移植性,那就可以了。
但是想象一下带有 32 位浮点数的 64 位架构。编译器可能会在 64 位边界上对齐结构的浮点数,并且您的
p[1]
会给你垃圾,并且
p[2]
会给你你认为你从中得到的东西
p[1]
&C。
但是,您的编译器可能会给您一些打包结构的方法。它仍然不是“标准”——标准没有提供这样的东西,不同的编译器提供了非常不兼容的方法——但它可能更便携。
让我们看一下 Doom III 源代码:
class idVec4 {
public:
float x;
float y;
float z;
float w;
...
const float * ToFloatPtr( void ) const;
float * ToFloatPtr( void );
...
}
ID_INLINE const float *idVec4::ToFloatPtr( void ) const {
return &x;
}
ID_INLINE float *idVec4::ToFloatPtr( void ) {
return &x;
}
它适用于许多系统。
您的代码没问题(只要它只处理在同一环境中生成的数据)。如果它是 POD,该结构将按照声明的方式在内存中布局。但是,一般来说,您需要注意一个问题:编译器会将填充插入到结构中,以确保遵守每个成员的对齐要求。
如果你的例子是
struct xyz
{
float x;
bool y;
float z;
};
那么 z 会在结构中开始 8 个字节,并且 sizeof(xyz) 会是 12,因为float
s (通常)是 4 字节对齐的。
同样,在这种情况下
struct xyz
{
float x;
bool y;
};
sizeof(xyz) == 8,以确保 ((xyz*)ptr)+1 返回一个符合 x 对齐要求的指针。
由于对齐要求/类型大小可能因编译器/平台而异,因此此类代码通常不可移植。
正如其他人指出的那样,规范不保证对齐。许多人说它依赖于硬件,但实际上它也依赖于编译器。硬件可能支持许多不同的格式。我记得 PPC 编译器支持编译指示如何“打包”数据。您可以将其打包在“本机”边界或将其强制为 32 位边界等。
很高兴了解您要做什么。如果您尝试“解析”输入数据,最好使用真正的解析器。如果要序列化,请编写一个真正的序列化程序。如果您尝试旋转诸如驱动程序之类的位,则设备规范应为您提供要写入的特定内存映射。然后您可以编写您的 POD 结构,指定正确的对齐编译指示(如果支持)并继续。
#pragma pack
在 MSVC 中)http://msdn.microsoft.com/en-us/library/aa273913%28v=vs.60%29.aspx__declspec(align(
在 MSVC 中)http://msdn.microsoft.com/en-us/library/83ythb65.aspx有两个因素会破坏你的假设。浮点数通常为 4 字节宽,因此很少会错位如此大的变量。但是仍然很容易破坏您的代码。
当二进制读取带有短裤的标头结构(如 BMP 或 TGA)时,此问题最为明显 - 忘记pack 1
会导致灾难。
我假设你想要一个结构来保持你的坐标作为成员(.x、.y 和 .z)被访问,但你仍然希望它们被访问,比方说,一种 OpenGL 方式(就像它是一个数组一样)。
您可以尝试实现结构的 [] 运算符,以便可以将其作为数组进行访问。就像是:
struct xyz
{
float x, y, z;
float& operator[] (unsigned int i)
{
switch (i)
{
case 0:
return x;
break;
case 1:
return y;
break;
case 2:
return z;
break;
default:
throw std::exception
break;
}
}
};