Variadic Struct 是臃肿的,在结构的末尾添加了额外的填充
Variadic Struct is Bloated, Extra Padding Being Added At The End of the Struct
我一直在关注 this tutorial for creating a variadic structure, which is nearly identical to another tutorial 从头开始创建基本元组。不幸的是,当我分析可变参数结构时,它似乎效率很低。结构的大小似乎臃肿,因为结构的大小似乎与其变量布局不匹配。字节对齐似乎不是问题,因为实际的元组似乎没有受到这种影响,所以我想知道它们是如何绕过它的,或者我在我的结构中做错了什么。
下面是我用来测试可变参数结构的代码:
#include <iostream>
#include <tuple>
#include <array>
template<typename ... T>
struct DataStructure
{
};
template<typename T>
struct DataStructure<T> {
DataStructure(const T& first) : first(first)
{}
DataStructure() {}
T first;
};
template<typename T, typename ... Rest>
struct DataStructure<T, Rest ...>
{
DataStructure(const T& first, const Rest& ... rest)
: first(first)
, rest(rest...)
{}
DataStructure() {}
T first;
[[no_unique_address]] DataStructure<Rest ... > rest;
};
struct test1 {
int one;
float two;
};
struct test2 {
double three;
float two;
int one;
};
int main()
{
std::cout << "Size of test1 with double: " << sizeof(test1) << std::endl;
std::cout << "Offset of test1 with double: " << offsetof(test1, one) << " | " << offsetof(test1, two) << std::endl;
std::cout << std::endl;
typedef DataStructure<int32_t, float> def;
std::cout << "Size of DataStructure<int32_t, float> w/o Double: " << sizeof(def) << std::endl;
std::cout << "Offset of DataStructure<int32_t, float> w/o Double: " << offsetof(def, first) << " | " << offsetof(def, rest.first) << std::endl;
std::cout << std::endl;
std::cout << "Size of test2 with double: " << sizeof(test2) << std::endl;
std::cout << "Offset of test2 with double: " << offsetof(test2, one) << "(int32) | " << offsetof(test2, two) << "(float) | " << offsetof(test2, three) << "(double)" << std::endl;
std::cout << std::endl;
typedef DataStructure<double, float, int32_t> defDouble;
std::cout << "Size of DataStructure<double, float, int32_t>: " << sizeof(defDouble) << std::endl;
std::cout << "Offset of DataStructure<double, float, int32_t>: " << offsetof(defDouble, rest.rest.first) << "(int32) | " << offsetof(defDouble, rest.first) << "(float) | " << offsetof(defDouble, first) << "(double)" << std::endl;
std::cout << std::endl;
std::tuple<int32_t, float, double> tp;
std::cout << "Size of tuple with double (gcc compiled tuple reverses parameter layout in memory): " << sizeof(tp) << std::endl;
std::cout << "Offset of tuple with double: " << (long)&std::get<0>(tp) - (long)&tp << "(int32) | " << (long)&std::get<1>(tp) - (long)&tp << "(float) | " << (long)&std::get<2>(tp) - (long)&tp << "(double)" << std::endl;
std::cout << std::endl;
std::cout << "Size of no parameter DataStructure<>: " << sizeof(DataStructure<>) << std::endl;
typedef DataStructure<int32_t, float, double> defDoubleNormal;
std::cout << "Size of DataStructure<int32_t, float, double>: " << sizeof(defDoubleNormal) << std::endl;
std::cout << "Offset of DataStructure<int32_t, float, double>: " << offsetof(defDoubleNormal, first) << "(int32) | " << offsetof(defDoubleNormal, rest.first) << "(float) | " << offsetof(defDoubleNormal, rest.rest.first) << "(double)" << std::endl;
std::cout << std::endl;
std::tuple<double, float, int32_t> tp2;
std::cout << "Size of tuple with double (gcc compiled tuple reverses parameter layout in memory): " << sizeof(tp) << std::endl;
std::cout << "Offset of tuple with double: " << (long)&std::get<0>(tp2) - (long)&tp2 << "(double) | " << (long)&std::get<1>(tp2) - (long)&tp2 << "(float) | " << (long)&std::get<2>(tp2) - (long)&tp2 << "(int32)" << std::endl;
std::cout << std::endl;
std::array<defDouble, 2> arr;
std::cout << "Array DataStructure<double, float, int32_t> Offsets: [0] - " << (long)&arr[0] - (long)&arr << ", [1] - " << (long)&arr[1] - (long)&arr;
}
上面的代码打印:
Size of test1 with double: 8
Offset of test1 with double: 0 | 4
Size of DataStructure<int32_t, float> w/o Double: 8
Offset of DataStructure<int32_t, float> w/o Double: 0 | 4
Size of test2 with double: 16
Offset of test2 with double: 12(int32) | 8(float) | 0(double)
Size of DataStructure<double, float, int32_t>: 16
Offset of DataStructure<double, float, int32_t>: 12(int32) | 8(float) | 0(double)
Size of tuple with double (gcc compiled tuple reverses parameter layout in memory): 16
Offset of tuple with double: 12(int32) | 8(float) | 0(double)
Size of no parameter DataStructure<>: 1
Size of DataStructure<int32_t, float, double>: 24
Offset of DataStructure<int32_t, float, double>: 0(int32) | 8(float) | 16(double)
Size of tuple with double (gcc compiled tuple reverses parameter layout in memory): 16
Offset of tuple with double: 8(double) | 4(float) | 0(int32)
Array DataStructure<double, float, int32_t> Offsets: [0] - 0, [1] - 16
我颠倒了 DataStructure with double 和元组之间的模板参数的顺序,因为我在内部使用的元组颠倒了成员的顺序(这是元组的一个未指定的实现细节,也是 gcc 实现它的方式);这样,两个成员布局的顺序相同,double 从偏移量 0 开始,然后是它们的 float,最后是它们的 int32。在 test2 结构中可以直观地看到该布局我们可以看到,“test2”所示结构的适当大小为 16,成员的偏移量对于双精度为 0,对于浮点为 8,对于 12对于 int32。元组显示相同的东西,尽管成员顺序颠倒,double 为 0,float 为 8,int32 为 12,总大小为 16 字节。这些相同的偏移量在 DataStructure 中用 double 找到; double 的偏移量为 0,float 的偏移量为 8,int32 的偏移量为 12,但这次总大小为 24 字节,我无法理解在结构末尾填充的内容或原因。我知道双打需要 8 字节对齐,但这在这里应该不是问题,而且显然不是元组和 test2 的情况。
即使是一个空的class也需要space来存储它自己,因此class的最小大小是1
。由于您的无参数 DataStructure
class 是空的并且它占用了一个成员 space 并导致其余成员占用更多 space 以允许对齐。使基础非空解决了这个问题:
template<typename ... T>
struct DataStructure;
template<typename T>
struct DataStructure<T>
{
DataStructure(const T& first)
: first(first)
{}
T first;
};
template<typename T, typename ... Rest>
struct DataStructure<T, Rest ...>
{
DataStructure(const T& first, const Rest& ... rest)
: first(first)
, rest(rest...)
{}
T first;
DataStructure<Rest ... > rest;
};
这仍然在 DataStructure<int32_t, float, double>
的情况下引入了额外的填充。这是因为您的代码本质上会产生:
struct A
{
double a;
};
struct B
{
A a;
float b;
};
struct C
{
B b;
int32_t c;
};
编译器将尝试始终将 double
放在 8 字节的倍数上,因为 B
需要 12 个字节,编译器将其填充到 [=17] 数组中的 16 个字节=], a
始终对齐到 8 个字节。 sizeof(B)
因此将是 16,导致 sizeof(C)
是 24。
我认为 std::tuple
通常是使用基础 classes 而不是成员来实现的。更改您的实现以执行此操作也可以解决此填充问题:
template<typename ... T>
struct DataStructure;
template<typename T>
struct DataStructure<T>
{
DataStructure(const T& first)
: first(first)
{}
T first;
};
template<typename T, typename ... Rest>
struct DataStructure<T, Rest ...>: DataStructure<Rest ... >
{
DataStructure(const T& first, const Rest& ... rest)
: first(first), DataStructure<Rest ... >(rest...)
{}
T first;
};
我一直在关注 this tutorial for creating a variadic structure, which is nearly identical to another tutorial 从头开始创建基本元组。不幸的是,当我分析可变参数结构时,它似乎效率很低。结构的大小似乎臃肿,因为结构的大小似乎与其变量布局不匹配。字节对齐似乎不是问题,因为实际的元组似乎没有受到这种影响,所以我想知道它们是如何绕过它的,或者我在我的结构中做错了什么。
下面是我用来测试可变参数结构的代码:
#include <iostream>
#include <tuple>
#include <array>
template<typename ... T>
struct DataStructure
{
};
template<typename T>
struct DataStructure<T> {
DataStructure(const T& first) : first(first)
{}
DataStructure() {}
T first;
};
template<typename T, typename ... Rest>
struct DataStructure<T, Rest ...>
{
DataStructure(const T& first, const Rest& ... rest)
: first(first)
, rest(rest...)
{}
DataStructure() {}
T first;
[[no_unique_address]] DataStructure<Rest ... > rest;
};
struct test1 {
int one;
float two;
};
struct test2 {
double three;
float two;
int one;
};
int main()
{
std::cout << "Size of test1 with double: " << sizeof(test1) << std::endl;
std::cout << "Offset of test1 with double: " << offsetof(test1, one) << " | " << offsetof(test1, two) << std::endl;
std::cout << std::endl;
typedef DataStructure<int32_t, float> def;
std::cout << "Size of DataStructure<int32_t, float> w/o Double: " << sizeof(def) << std::endl;
std::cout << "Offset of DataStructure<int32_t, float> w/o Double: " << offsetof(def, first) << " | " << offsetof(def, rest.first) << std::endl;
std::cout << std::endl;
std::cout << "Size of test2 with double: " << sizeof(test2) << std::endl;
std::cout << "Offset of test2 with double: " << offsetof(test2, one) << "(int32) | " << offsetof(test2, two) << "(float) | " << offsetof(test2, three) << "(double)" << std::endl;
std::cout << std::endl;
typedef DataStructure<double, float, int32_t> defDouble;
std::cout << "Size of DataStructure<double, float, int32_t>: " << sizeof(defDouble) << std::endl;
std::cout << "Offset of DataStructure<double, float, int32_t>: " << offsetof(defDouble, rest.rest.first) << "(int32) | " << offsetof(defDouble, rest.first) << "(float) | " << offsetof(defDouble, first) << "(double)" << std::endl;
std::cout << std::endl;
std::tuple<int32_t, float, double> tp;
std::cout << "Size of tuple with double (gcc compiled tuple reverses parameter layout in memory): " << sizeof(tp) << std::endl;
std::cout << "Offset of tuple with double: " << (long)&std::get<0>(tp) - (long)&tp << "(int32) | " << (long)&std::get<1>(tp) - (long)&tp << "(float) | " << (long)&std::get<2>(tp) - (long)&tp << "(double)" << std::endl;
std::cout << std::endl;
std::cout << "Size of no parameter DataStructure<>: " << sizeof(DataStructure<>) << std::endl;
typedef DataStructure<int32_t, float, double> defDoubleNormal;
std::cout << "Size of DataStructure<int32_t, float, double>: " << sizeof(defDoubleNormal) << std::endl;
std::cout << "Offset of DataStructure<int32_t, float, double>: " << offsetof(defDoubleNormal, first) << "(int32) | " << offsetof(defDoubleNormal, rest.first) << "(float) | " << offsetof(defDoubleNormal, rest.rest.first) << "(double)" << std::endl;
std::cout << std::endl;
std::tuple<double, float, int32_t> tp2;
std::cout << "Size of tuple with double (gcc compiled tuple reverses parameter layout in memory): " << sizeof(tp) << std::endl;
std::cout << "Offset of tuple with double: " << (long)&std::get<0>(tp2) - (long)&tp2 << "(double) | " << (long)&std::get<1>(tp2) - (long)&tp2 << "(float) | " << (long)&std::get<2>(tp2) - (long)&tp2 << "(int32)" << std::endl;
std::cout << std::endl;
std::array<defDouble, 2> arr;
std::cout << "Array DataStructure<double, float, int32_t> Offsets: [0] - " << (long)&arr[0] - (long)&arr << ", [1] - " << (long)&arr[1] - (long)&arr;
}
上面的代码打印:
Size of test1 with double: 8
Offset of test1 with double: 0 | 4
Size of DataStructure<int32_t, float> w/o Double: 8
Offset of DataStructure<int32_t, float> w/o Double: 0 | 4
Size of test2 with double: 16
Offset of test2 with double: 12(int32) | 8(float) | 0(double)
Size of DataStructure<double, float, int32_t>: 16
Offset of DataStructure<double, float, int32_t>: 12(int32) | 8(float) | 0(double)
Size of tuple with double (gcc compiled tuple reverses parameter layout in memory): 16
Offset of tuple with double: 12(int32) | 8(float) | 0(double)
Size of no parameter DataStructure<>: 1
Size of DataStructure<int32_t, float, double>: 24
Offset of DataStructure<int32_t, float, double>: 0(int32) | 8(float) | 16(double)
Size of tuple with double (gcc compiled tuple reverses parameter layout in memory): 16
Offset of tuple with double: 8(double) | 4(float) | 0(int32)
Array DataStructure<double, float, int32_t> Offsets: [0] - 0, [1] - 16
我颠倒了 DataStructure with double 和元组之间的模板参数的顺序,因为我在内部使用的元组颠倒了成员的顺序(这是元组的一个未指定的实现细节,也是 gcc 实现它的方式);这样,两个成员布局的顺序相同,double 从偏移量 0 开始,然后是它们的 float,最后是它们的 int32。在 test2 结构中可以直观地看到该布局我们可以看到,“test2”所示结构的适当大小为 16,成员的偏移量对于双精度为 0,对于浮点为 8,对于 12对于 int32。元组显示相同的东西,尽管成员顺序颠倒,double 为 0,float 为 8,int32 为 12,总大小为 16 字节。这些相同的偏移量在 DataStructure 中用 double 找到; double 的偏移量为 0,float 的偏移量为 8,int32 的偏移量为 12,但这次总大小为 24 字节,我无法理解在结构末尾填充的内容或原因。我知道双打需要 8 字节对齐,但这在这里应该不是问题,而且显然不是元组和 test2 的情况。
即使是一个空的class也需要space来存储它自己,因此class的最小大小是1
。由于您的无参数 DataStructure
class 是空的并且它占用了一个成员 space 并导致其余成员占用更多 space 以允许对齐。使基础非空解决了这个问题:
template<typename ... T>
struct DataStructure;
template<typename T>
struct DataStructure<T>
{
DataStructure(const T& first)
: first(first)
{}
T first;
};
template<typename T, typename ... Rest>
struct DataStructure<T, Rest ...>
{
DataStructure(const T& first, const Rest& ... rest)
: first(first)
, rest(rest...)
{}
T first;
DataStructure<Rest ... > rest;
};
这仍然在 DataStructure<int32_t, float, double>
的情况下引入了额外的填充。这是因为您的代码本质上会产生:
struct A
{
double a;
};
struct B
{
A a;
float b;
};
struct C
{
B b;
int32_t c;
};
编译器将尝试始终将 double
放在 8 字节的倍数上,因为 B
需要 12 个字节,编译器将其填充到 [=17] 数组中的 16 个字节=], a
始终对齐到 8 个字节。 sizeof(B)
因此将是 16,导致 sizeof(C)
是 24。
我认为 std::tuple
通常是使用基础 classes 而不是成员来实现的。更改您的实现以执行此操作也可以解决此填充问题:
template<typename ... T>
struct DataStructure;
template<typename T>
struct DataStructure<T>
{
DataStructure(const T& first)
: first(first)
{}
T first;
};
template<typename T, typename ... Rest>
struct DataStructure<T, Rest ...>: DataStructure<Rest ... >
{
DataStructure(const T& first, const Rest& ... rest)
: first(first), DataStructure<Rest ... >(rest...)
{}
T first;
};