Memory layout of Simple class (struct)

Dear experts,

I have shallow knowledge about memory layout of class, such as alignment,
and member function is placed on like static memory area.

I think that this point is important for cache-centric manners and SIMD parallelization.

Here, memory arrangement of the below class is arranged to be contiguous memory layout without alignment(no padding) based on C++ specification?
Or, hardware dependency?

1
2
3
4
5
6
7
8
9
10
11

class VectorD{
  double x,y,z;
  // the following consists of just member functions
}

class VectorF{
  float x,y,z;
  // the following consists of just member functions
}


double is 8 byte, and float is 4 byte, I suppose if there is no padding.
(If not, should I use array of floating type like double[3]?)

Kind regards


different compilers align differently, with different padding, and some have options to over-ride this (but that can cause inefficency if you mess with it incorrectly).
an array forces you to do extra instructions, you have to do a offset+address to get to each location... so that may help align but cost more per access if its not necessary.
> Here, memory arrangement of the below class is arranged to be contiguous memory layout
> without alignment(no padding) based on C++ specification?

In theory, no. The only explicitly stated guarantee is that for standard layout types, the address of the object is the same as the address of its first non-static member object.

In practice, I would say yes. An implementation would introduce un-named padding within the object only if it is necessary for achieving correct alignment.

Note: std::complex<T> is a special case; the standard explicitly states that it can safely be accessed as if it were an array of two T.
https://eel.is/c++draft/complex.numbers#general-4


> should I use array of floating type like double[3]?

Yes, That would be the clean option. With

1
2
3
4
5
6
7
8
9
struct VectorD {

     double d[3] ;
     
     double& x() { return d[0] ; }
     const double& x() const { return d[0] ; }
     
     // etc.
};


there is the guarantee that the address of the object is the same as the address of its array member (the first non-static member object). No extra instructions would be needed to access it as an array. The compiler obviously knowns the layout; even if it was not an array, the address + constexpr offset computation would be required anyway for accessing non-static members (other than the first one) of the class.
Ok, I can see that it can avoid the second memory offset.
But the object's members are in the same cache page anyway. Did we gain anything with the array (I see that we did not lose anything)? Maybe it would help if the objects were very large?
Last edited on
Dear jonnin and JLBorges

Thank you for your profound answers.

I understand that array is an obvious solution to avoid problems of memory layouts.


But, array-use codes entails refactoring of my codes, it is take a bit time.
In practice, I would say yes. An implementation would introduce un-named padding within the object only if it is necessary for achieving correct alignment.

if "sizeof(Vector)" is equal to no-padding size, is it regarded as almost the same with data structure of array-use code?
(I want to use it for early peformance estimation)

But the object's members are in the same cache page anyway. Did we gain anything with the array (I see that we did not lose anything)? Maybe it would help if the objects were very large?



It is useful comment for me, even if there is padding, they can exist in thesame cache page, so that it works .
The above object, Vector, is often used as large size of std::vector<Vector>. Therefore, as possible as I can, I want to make them compact.

Kind regards



> if "sizeof(Vector)" is equal to no-padding size, is it regarded as almost the same with data structure of array-use code?

Yes.
In that case, accessing its members as if they were sub-objects of an array would not violate the strict aliasing rule.

For example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
struct VectorD {

  double x, y, z ;

  using as_array_t = double[3] ; // type alias for viewing the members as array of double

};

VectorD::as_array_t& view_as_array( VectorD& vec ) {

    static_assert( sizeof(VectorD) == sizeof(VectorD::as_array_t) ) ;
    return reinterpret_cast< VectorD::as_array_t& >(vec) ;
}

const VectorD::as_array_t& view_as_array( const VectorD& vec ) {

    static_assert( sizeof(VectorD) == sizeof(VectorD::as_array_t) ) ;
    return reinterpret_cast< const VectorD::as_array_t& >(vec) ;
}

int main()
{
    VectorD vec ;
    auto& arr = view_as_array(vec) ;
    arr[0] = arr[1] = arr[2] = 0 ;
}
Dear JLBorges

Thank you for your precise answer.

I will try some performance enhancement by my legacy code at first step.

I appreciate your helpful comments.

Kind regards
Topic archived. No new replies allowed.