3

I have a situation similar to this one

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

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

Father tilemap[WIDTH][HEIGHT];

Now I just realized I would like to save four bytes for x,y which are set always to the same values for both children of the same father.

All around my code I pass around many Father* and many Child* while recovering coordinates with father->child1->x or child1->x respectively. I would like to safely move the coordinates at Father level but I'm unsure about some facts.

Will the order of declared fields be respected versus any optimization or possible implementation of gcc/g++? Can I be confident that &father == &father.child1?

The real issue here is that I pass Child* without knowing if it's a child1 or child2 field so I cannot directly know the offset to recover address of father (and coordinates consequently).. I was wondering to use a bit at Child level to distinguish them but will I be easily able to recover address of father then?

Any suggestion would be appreciated, thanks

EDIT: just as a further info, I'm using C++ as my main language but these structs don't contain ANY strange methods, just fields and empty constructor.

Jack
  • 131,802
  • 30
  • 241
  • 343
  • See http://stackoverflow.com/questions/281045/do-class-struct-members-always-get-created-in-memory-in-the-order-they-were-decl – Ian Clelland Feb 02 '12 at 15:35

6 Answers6

8

The general rules about field layout in C are:

  1. The address of the first member is the same as the address of the struct itself. That is, the offsetof of the member field is 0.
  2. The addresses of the members always increase in declaration order. That is, the offsetof of the n-th field is lower than that of the (n+1)-th member.

In C++, of course, that is only true if it is a standard layout type, that is roughly, a class or struct with no public/private/protected mixed members, no virtual functions and no members inherited from other classes.

rodrigo
  • 94,151
  • 12
  • 143
  • 190
  • I'd like mention the difference between something the standard assures, and something it's good practice to rely on. In this case, I think relying on `&father==&father.child` is bad practice. – ugoren Feb 02 '12 at 16:05
  • @ugoren - Bad practice is a relative term. Doing this just for fun is a bad idea. But in C it is frequently done to implement a kind of type-inheritance. And in low-level programming, as the OP seems to be doing, is also frequent to rely in these facts, and even more not so portable, for the sake of speed or simplicity. – rodrigo Feb 02 '12 at 19:39
  • 1
    You're right that bad practice is relative, and more specifically, a question of alternatives. In most cases, replacing `(Child *)father` with `&father->child1` would give better code. In the reverse case, you should use `offsetof` (even if you know it's 0). But if there are cases where, try as you may, you have to rely on this, then OK. – ugoren Feb 02 '12 at 19:46
2

Disclaimer: Partial answer. C++ only

Will the order of declared fields be respected versus any optimization or possible implementation of gcc/g++?

The order of the members in the memory layout will not be tampered with by the compiler. It's the same order you declared the members in.

Can I be confident that &father == &father.child1?

In this particular case, yes. But it does not follow from the mere fact that child1 is the first member of father that &father == &father.child1?. This is true only if father is a POD, which in this case it is.

Community
  • 1
  • 1
Armen Tsirunyan
  • 130,161
  • 59
  • 324
  • 434
  • Will having some union bit fields always AFTER whole data fields alter this fact? – Jack Feb 02 '12 at 15:44
  • @Jack: No, it will not. Seth explains why below :) vvv – Armen Tsirunyan Feb 02 '12 at 15:46
  • @Jack as long as `Father` has a trivial constructor and destructor and contains only POD types then it is a POD type, so containing a `union` will not change it – Seth Carnegie Feb 02 '12 at 15:46
  • Actually, `Father` need only be a _standard-layout_ class. Also, `&father == &father.child1` will not even compile, so it is necessary to `reinterpret_cast` one or more of the operands. – Mankarse Feb 02 '12 at 15:48
1

The pertinent section of the C standard says this (emphasis mine):

Within a structure object, the non-bit-field members and the units in which bit-fields reside have addresses that increase in the order in which they are declared. A pointer to a structure object, suitably converted, points to its initial member (or if that member is a bit-field, then to the unit in which it resides), and vice versa. There may be unnamed padding within a structure object, but not at its beginning.

The C++ standard makes the same promise:

A pointer to a standard-layout struct object, suitably converted using a reinterpret_cast, points to its initial member (or if that member is a bit-field, then to the unit in which it resides) and vice versa. [ Note: There might therefore be unnamed padding within a standard-layout struct object, but not at its beginning, as necessary to achieve appropriate alignment. —end note ]


So when you ask:

Can I be confident that &father == &father.child1?

The answer is yes.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
0

If you decide to rely on any compiler specific behaviour then the best you can do is to add static asserts for any assumptions you are relying on. This way, if anything changes about the layout due to compiler upgrade, compile options, or pragmas elsewhere in code, you will be told at compile time straight away, and exactly what the problem is. This then would serve as a basis for porting to other platforms too if that is a requirement.

Examples in this Q: What does static_assert do, and what would you use it for?

Community
  • 1
  • 1
Gaspode
  • 362
  • 4
  • 8
0

Try the following

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

You should be careful not to pass a Child which isn't incorporated in a corresponding Father, in which case you'll get a bogus address in father_p and (maybe) corrupt your memory.

Michael Pankov
  • 3,581
  • 2
  • 23
  • 31
0

Instead of relying on memory layout, have you considered using something along the lines of the Flyweight pattern?

Instead of storing two Childs in Father, you can store two stripped-down BasicChilds (that don't have x,y data) and generate full-fledged Childs on-the-fly as needed:

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_;
};
Emile Cormier
  • 28,391
  • 15
  • 94
  • 122