C / C ++结构中的字段顺序

时间:2012-02-02 15:30:49

标签: c++ c pointers struct

我的情况类似于这个

struct Child
{
  u16 x, y;
  // other fields
};

struct Father
{
  struct Child child1;
  struct Child child2;
  // other fields
};

Father tilemap[WIDTH][HEIGHT];

现在我意识到我想为x,y保存四个字节,这些字节总是为同一个父亲的两个孩子设置相同的值。

在我的代码周围,我分别通过Father*Child*恢复了许多father->child1->x和许多child1->x。我想安全地移动Father级别的坐标,但我不确定某些事实。

与gcc / g ++的任何优化或可能的实现相比,是否会尊重声明字段的顺序?我能相信&father == &father.child1吗?

这里的真正问题是我传递Child*而不知道它是否是child1或child2字段,因此我无法直接知道偏移量来恢复父亲的地址(因此也是坐标)。我想知道使用一个位于Child级以区分它们但我能否轻松恢复父亲的地址呢?

任何建议都将不胜感激,谢谢

编辑:作为进一步的信息,我使用C ++作为我的主要语言,但这些结构不包含任何奇怪的方法,只包含字段和空构造函数。

6 个答案:

答案 0 :(得分:4)

关于C中字段布局的一般规则是:

  1. 第一个成员的地址与结构本身的地址相同。也就是说,成员字段的offsetof为0。
  2. 成员的地址总是按声明顺序增加。也就是说,第n个字段的offsetof低于第(n + 1)个成员的{{1}}。
  3. 当然,在C ++中,只有当它是标准布局类型时才会出现这种情况,大致是一个没有公共/私有/受保护混合成员的类或结构,没有虚函数和没有成员继承自其他类。

答案 1 :(得分:2)

免责声明:部分答案。仅限C ++

  

与任何优化相比,是否会尊重声明字段的顺序   或者可能实现gcc / g ++?

编译器不会篡改内存布局中成员的顺序。这与你宣布成员的顺序相同。

  

我能否相信& father ==& father.child1?

在这种特殊情况下,是的。但它并不仅仅是因为child1是&father == &father.child1?父亲的第一个成员。只有当父亲是POD时才会这样,在这种情况下它是。{/ p>

答案 2 :(得分:1)

C标准的相关部分说明了这一点(强调我的):

  

在结构对象内,非位字段成员和位域中的单位   驻留的地址按声明的顺序增加。 指向a的指针   结构对象,适当转换,指向其初始成员(或者如果该成员是a   比特字段,然后到它所在的单位),反之亦然。可能有未命名的   在结构对象中填充,但不在其开头。

C ++标准做出了同样的承诺:

  

指向标准布局结构对象的指针,使用reinterpret_cast进行适当转换,指向它   初始成员(或者如果该成员是位字段,则指向它所在的单位),反之亦然。 [ 注意:   因此,在标准布局结构对象中可能存在未命名的填充,但不是在其开头,   必要时,以实现适当的对齐。 - 后注]


所以当你问:

  

我能相信&father == &father.child1吗?

答案是肯定的。

答案 3 :(得分:0)

尝试以下

struct Child
{
  int isChild1;
  u16 x, y;
  // other fields
};
...
Father *father_p;
if (*child_p).isChild1
   father_p = child_p;
else
   father_p = child_p - sizeof(struct Child);
(*father_p).x = ... // whatever you want to do with coordinates

你应该小心不要传递一个未包含在相应父亲中的孩子,在这种情况下,你会在father_p中得到一个虚假的地址,并且(可能)会破坏你的记忆。

答案 4 :(得分:0)

您是否考虑过使用Flyweight模式的某些内容,而不是依赖于内存布局?

不是在父亲中存储两个Childs,而是可以存储两个精简的BasicChild(没有x,y数据)并根据需要即时生成完整的Childs:

struct BasicChild
{
    float foo;
    float bar;
    void printPosition(int x, int y) {std::cout << x << "," << y << "\n";}
};

struct Child
{
    Child(BasicChild basicChild, int x, int y)
        : basicChild_(basicChild), x_(x), y_(y){}
    float foo() {return basicChild_.foo;}
    float bar() {return basicChild_.bar;}
    void printPosition() {basicChild_.printPosition(x_, y_);}

private:
    int x_, y_;
    BasicChild basicChild_;
};

struct Father
{
    Child child1() {return Child(child1_, x_, y_);}
    Child child2() {return Child(child2_, x_, y_);}

private:
    int x_, y_;
    BasicChild child1_;
    BasicChild child2_;
};

答案 5 :(得分:0)

如果您决定依赖任何编译器特定的行为,那么您可以做的最好的事情是为您依赖的任何假设添加静态断言。这样,如果由于编译器升级,编译选项或代码中其他地方的编译指示而导致布局发生任何变化,您将立即在编译时告知,确切地说是问题所在。如果这是一项要求,那么这也将成为移植到其他平台的基础。

此Q中的示例:What does static_assert do, and what would you use it for?