The following code snips does NOT create a level of indirection. You can see it by introspect the assembler code.
So don't worry, start structure your data. See the next post for a practice example.
Example 1
Lets begin with a simple struct contains 3 members: double, float, int. Sum them up. And see what the compile generate
struct A {
double d;
float f;
int i;
};
static_assert(sizeof(A) == 16);
auto func(const A& a){
return a.d + a.f + a.i;
}
With the static assert sizeof you can be sure there is no padding in the data structure. The generate ASM code compiled with GCC 8.1 -O1 I comment it to make it easier for you.
func(A const&):
pxor %xmm0, %xmm0 # xmm0 = 0
cvtss2sd 8(%rdi), %xmm0 # convert float to double and store it in xmm0
addsd (%rdi), %xmm0 # add double and the converted float to xmm0
pxor %xmm1, %xmm1 # xmm1 = 0
cvtsi2sd 12(%rdi), %xmm1 # convert integer to double and store it in xmm1
addsd %xmm1, %xmm0 # add the already added float+double to the converted integer to xmm0
ret # return xmm0 as result
You can see two conversions from int/float to double and two additions. You can also see the offsets. The double value has offset 0, the float value 8 bytes and the integer value (8+4)=12 bytes. This is quite straight. So add more code.
Example 2
This example create a nested struct Bdata inside struct B. Sum the values up again and see what the compiler generate.
struct B {
struct Bdata {
double d;
float f;
int i;
};
Bdata b;
};
static_assert(sizeof(B) == 16);
auto func2(const B& a){
return a.b.d + a.b.f + a.b.i;
}
It is exactly, 100%, the same assembler code as example1 ! So i don't show it here again. The memory layout of struct B is the same as struct A, so the same ASM code is generated. No overhead by the extra variable b.
Example 3
In the next example a std::tuple is used to store the 3 values. The access is provided with the function std::get. It looks different. But wait...
struct C {
std::tuple<double, float, int> c;
};
static_assert(sizeof(C) == 16);
auto func3(const C& a){
return std::get<0>(a.c) + std::get<1>(a.c) + std::get<2>(a.c);
}
This time, the generated code is different
func3(C const&):
pxor %xmm0, %xmm0 # xmm0 = 0
cvtss2sd 4(%rdi), %xmm0 # convert float to double and store it in xmm0
addsd 8(%rdi), %xmm0 # add double and the converted float to xmm0
pxor %xmm1, %xmm1 # xmm1 = 0
cvtsi2sd (%rdi), %xmm1 # convert integer to double and store it in xmm1
addsd %xmm1, %xmm0 # add the already added float+double to the converted integer to xmm0
ret # return xmm0 as result
But if you take a closer look, it is still the same! Only the memory layout has changed. First the integer, then the float, then the double. A std::stuple store it's values in reverse order! But the generated assembler code is 100% identical to the first example!
Example 4
In the last example I change the structure a little bit. But you can guess, the result is still the same.
struct D {
double d;
struct Ddata {
float f;
int i;
};
Ddata x;
};
static_assert(sizeof(D) == 16);
auto func4(const D& a){
return a.d + a.x.f + a.x.i;
}
See the next post for a practice example.