Align/offset specific members of a struct
Asked Answered
R

1

2

What is the best or conventional method to align members inside a structure? Is adding dummy arrays the best solution?

I have a struct of double and a triple of doubles?

struct particle{
  double mass;
  std::tuple<double, double, double> position;
}

If I have an array of these, the memory will look like this

[d][d  d  d][d][d  d  d][d][d  d  d]...

The problem is that the distance from the first triple to the second triple is not an integer multiple of sizeof(std::tuple<double, double,double>)==3*sizeof(double), therefore I cannot interpret the interleaved array of triples as an array with strides.

In other words, given an array of particles particle ps[100], I can to take the address of a member to the first element triple* v1P = &(ps[0].position) and I want that v1P + n == &(ps[1].position) for some (integer) n (that I can deduce at compile-time, for example n = sizeof(particle)/sizeof(tripe) if sizeof(particle)%sizeof(tripe)==0.)

What is the best method to force the triple to have a different offset?

I could stick a number of artificial doubles in the middle to match the offset:

struct particle{
  double mass;
  double garbage[2];
  std::tuple<double, double, double> position;
}

So memory will look like this

[d][*  *][d  d  d][d][*  *][d  d  d][d][*  *][d  d  d]...

but then I cannot use initializers (particle{1.,{1.,2.,3.}}).

I could also add it to the end

struct particle{
  double mass;
  std::tuple<double, double, double> position;
  double garbage[2];
}

So, I can keep using the initializer. Is this the best method? But it will only work in this simple case.

Another alternative could be to force alignment of the struct to some multiple of the 3*sizeof(double)

struct alignas(3*sizeof(double)) particle{double mass; std::tuple<...> position;};

but the problem is that it is not a power of 2, so GCC and clang reject it. Note that in other sizes of the tuple the alignas strategy works because it can solve the problem being accidentally a power of two.

For example in

struct alignas(4*sizeof(double)) particle{double mass; std::pair<double, double> position;};

alignas gives an easy solution for this other use case (in GCC).

Is there a general or accepted solution for this?

I also found about ((packed)), is that also necessary in this solution?


I found this old article https://www.embedded.com/design/prototyping-and-development/4008281/2/Padding-and-rearranging-structure-members , https://web.archive.org/web/20190101225513/http://www.drdobbs.com/cpp/padding-and-rearranging-structure-member/240007649 . It seems that adding arrays in the middle of was a solution back then at least. Moreover, I could use char[N] to have finer control, to the level of a byte in this way.

Round answered 9/8, 2019 at 5:54 Comment(6)
In this case I would suggest an array of double instead of a std::tuple. And you can still use strides, but you have to include the full size of the structures to calculate array offsets.Arthritis
And talking about strides and interleaved arrays, what is the real problem you need to solve? What is the structure for? How can a structure with two values for position be equal to the structure with three values? Also remember that the compiler is free to add its own padding between members or after the last member of a structure.Arthritis
@Someprogrammerdude, 1) I want to see a an array of particles also as a strided array of positions (with the normal pointer arithmetic for both). 2) Nevermind the "two" values, that was a mistake, the example is more interstring with triples. 3) Yes, I understand that the solutions will be compiler dependent, I will have to adjust the offsets for different compilers, fortunately I can have compile-time assertions to detect this problem during compilation. For example static_assert( sizeof(particle)%sizeof(triple) == 0 ).Round
If I understand correctly, what you want to do is undefined behavior in c++. I don't think accessing internal members of a array struct from flatted outer array is defined. you many use ((packed)) to force everything on defined strides and your code probably works without problems, but I still think it is undefined behavior in c++.Zip
@Afshin, certainly it is compiler-depedent behavior for the reason you mention, however I don't see where it is the undefined behavior: I can take an array of particles particle ps[100], I can take the address of a member to the first element triple* v1P = &(ps[0].position). Now, the only thing I want is that v1P + n = &(ps[1].position) for some (integer) n.Round
yea, I noticed your problem. But I think since each particle is array represent different data, accessing directly in this way should be undefined behavior. I may be wrong though.Zip
Z
1

Adding arrays in middle of structures is a traditional way to create correct padding for members of structure and there is no problem with it. I have a thread on using ((packed)) and ((aligned)) in case you want to align your structure as you like.

I may be wrong, but I think what you want to achieve is somehow undefined behavior for 2 reasons. If you want to access members of structure in array in the way you describe:

  1. You create relationship between different unrelated data members of array.
  2. You cannot use properties of array directly anymore. e.g how you understand you are at the last member of array? You need to compare address of members with ending address of array.

Though, if you set structure padding to 1 (with ((packed))) should guarantee what you want to do, but I personally don't suggest it.

Update following structure:

struct particle{
  double mass;
  __attribute__((aligned(2*sizeof(double)))) std::tuple<double, double, double> position;
};

gives you similar strides as:

struct particle{
  double mass;
  double garbage[2];
  std::tuple<double, double, double> position;
}

But since tuple<> is a non-POD, you cannot use ((packed)) attribute. Just got this warning myself trying to test it.

Zip answered 9/8, 2019 at 6:47 Comment(7)
Thanks, it is possible you are right wrt UB. Regarding ((aligned)), am I right so say that I can replace T[N]` by __attribute__((aligned(N*sizeof(T)))) in the subsequent member? I don't understand what role ((packed)) plays here.Round
I am asking how to replaced char[N] by ((aligned)) and attribute because I want to use struct-initialization without the intrusion of the artificial arrays (and no need have private data or an explicit constructor).Round
So, did you try to use __attribute__((packed, aligned(...? and you got a warning. What would have been the advantage of using packed? I think std::tuple<double, double, double> can be replaced by std::array<double, 3> which is a POD (?). I found out by trying that __attribute((... can be put at the end of the member, which I find is less intrusive. Finally, I don't understand where the 2 comes from, it is because it need 2 doubles to shift the offset of the position member?Round
@Round ((packed)) may not be useful for you. Because it is used to decrease spacing between members, so you can have a smaller structure(in case size of structure is important). But you just want to have members in specific position to access them using strides. and yea, 2 comes from what you got yourself. But as i mentioned, try to change your design, so you don't need this strange member access.Zip
the design is kind of set by legacy C-functions over arrays, which in turn are Fortran-interfaces, in this world arrays, have integer strides (in the units of the same data type, not bytes).Round
Unfortunately these solutions are recognized as UB by de clang sanitizer #62318439. What worked and was to add dummy bytes here and there, for example struct employee{ std::string name; short salary; std::size_t age; char dummy[9]; employee() = default;}. Is there a better way along these lines?Round
I found this solution that is perhaps too fancy and it uses a gnu extension (it is also not very flexible because it works for one specific member) struct employee{ std::string name; union{ struct{ short salary; std::size_t age; }; char dummy[sizeof(std::string)]; }; employee(std::string name, short salary, std::size_t age) : name{name}, salary{salary}, age{age}{} }; Round

© 2022 - 2024 — McMap. All rights reserved.