6

让我们有一个简单的结构(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?
4

12 回答 12

10

这里的答案有点棘手。C++ 标准规定 POD 数据类型将具有 C 布局兼容性保证(参考)。根据 C 规范的第9.2节,结构的成员将按顺序排列,如果

  1. 没有可访问性修饰符区别
  2. 数据类型没有对齐问题

所以是的,只要类型float在当前平台上具有兼容的对齐方式(它是平台字长),这个解决方案就可以工作。所以这应该适用于 32 位处理器,但我的猜测是它对于 64 位处理器会失败。基本上任何sizeof(void*)不同于sizeof(float)

于 2010-01-18T18:53:19.273 回答
5

标准不保证这一点,并且不适用于许多系统。原因是:

  • 编译器可以根据目标平台调整结构成员,这可能意味着 32 位对齐、64 位对齐或其他任何内容。
  • 浮点数的大小可能是 32 位或 64 位。不能保证它与结构成员对齐相同。

这意味着它p[1]可能与 位于同一位置xyz.y,或者它可能部分重叠,或者根本不重叠。

于 2010-01-18T18:50:57.263 回答
4

不,除了第一个字段之外,这样做是不行的。

从 C++ 标准:

9.2 类成员
指向 POD 结构对象的指针,使用 reinterpret_cast 适当转换,指向其初始成员(或者如果该成员是位字段,则指向它所在的单元),反之亦然。[注意:因此,在 POD 结构对象中可能存在未命名的填充,但不是在其开头,这是实现适当对齐所必需的。

于 2010-01-18T18:50:28.373 回答
2

取决于硬件。该标准明确允许 POD 类具有未指定和不可预测的填充。我在 C++ Wikipedia 页面上注意到了这一点,并为您抓取了带有规范参考的脚注。

^ ab ISO/IEC (2003)。ISO/IEC 14882:2003(E):编程语言 - C++ §9.2 类成员 [class.mem] 段。17

然而,实际上,在常见的硬件和编译器上它会很好。

于 2010-01-18T18:50:50.573 回答
2

如有疑问,请更改数据结构以适应应用程序:

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];
}

一般来说,如果需要以两种或多种不同的方式处理数据结构,则可能需要重新设计数据结构;或代码。

于 2010-01-18T20:01:30.473 回答
1

该标准要求内存中的排列顺序与定义的顺序相匹配,但允许在它们之间进行任意填充。如果您在成员之间有访问说明符(public:,private:protected:),则即使是关于顺序的保证也会丢失。

编辑:在所有三个成员都属于相同原始类型(即它们本身不是结构或类似的东西)的特定情况下,您有相当大的机会——对于原始类型,对象的大小和对齐要求通常是相同的,所以它成功了。

OTOH,这只是偶然,而且往往更像是弱点而不是优势。代码是错误的,所以理想情况下它会立即失败而不是看起来工作,直到您为将成为您最重要客户的公司所有者进行演示的那一天,届时它将(当然)以最令人发指的方式失败......

于 2010-01-18T18:52:15.453 回答
1

不,您可能不会假设没有差距。您可以检查您的架构,如果没有并且您不关心可移植性,那就可以了。

但是想象一下带有 32 位浮点数的 64 位架构。编译器可能会在 64 位边界上对齐结构的浮点数,并且您的

p[1]

会给你垃圾,并且

p[2]

会给你你认为你从中得到的东西

p[1]

&C。

但是,您的编译器可能会给您一些打包结构的方法。它仍然不是“标准”——标准没有提供这样的东西,不同的编译器提供了非常不兼容的方法——但它可能更便携。

于 2010-01-18T18:59:00.910 回答
1

让我们看一下 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;
}

它适用于许多系统。

于 2012-02-03T08:57:36.087 回答
0

您的代码没问题(只要它只处理在同一环境中生成的数据)。如果它是 POD,该结构将按照声明的方式在内存中布局。但是,一般来说,您需要注意一个问题:编译器会将填充插入到结构中,以确保遵守每个成员的对齐要求。

如果你的例子是

struct xyz
{
    float x;
    bool y;
    float z;
};

那么 z 会在结构中开始 8 个字节,并且 sizeof(xyz) 会是 12,因为floats (通常)是 4 字节对齐的。

同样,在这种情况下

struct xyz
{
    float x;
    bool y;
};

sizeof(xyz) == 8,以确保 ((xyz*)ptr)+1 返回一个符合 x 对齐要求的指针。

由于对齐要求/类型大小可能因编译器/平台而异,因此此类代码通常不可移植。

于 2010-01-18T18:53:50.190 回答
0

正如其他人指出的那样,规范不保证对齐。许多人说它依赖于硬件,但实际上它也依赖于编译器。硬件可能支持许多不同的格式。我记得 PPC 编译器支持编译指示如何“打包”数据。您可以将其打包在“本机”边界或将其强制为 32 位边界等。

很高兴了解您要做什么。如果您尝试“解析”输入数据,最好使用真正的解析器。如果要序列化,请编写一个真正的序列化程序。如果您尝试旋转诸如驱动程序之类的位,则设备规范应为您提供要写入的特定内存映射。然后您可以编写您的 POD 结构,指定正确的对齐编译指示(如果支持)并继续。

于 2010-01-18T19:07:34.647 回答
0
  1. 结构包装(例如#pragma pack在 MSVC 中)http://msdn.microsoft.com/en-us/library/aa273913%28v=vs.60%29.aspx
  2. 变量对齐(例如__declspec(align(在 MSVC 中)http://msdn.microsoft.com/en-us/library/83ythb65.aspx

有两个因素会破坏你的假设。浮点数通常为 4 字节宽,因此很少会错位如此大的变量。但是仍然很容易破坏您的代码。

当二进制读取带有短裤的标头结构(如 BMP 或 TGA)时,此问题最为明显 - 忘记pack 1会导致灾难。

于 2012-07-16T08:51:24.230 回答
-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;
    }
  }
};
于 2012-06-21T17:33:43.000 回答