对齐支持
数据对齐
回忆一个经典案例:
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
| struct HowManyBytes{ char a; int b; }; int main(){ cout<<"sizeof(char):"<<sizeof(char)<<endl; cout<<"sizeof(int):"<<sizeof(int)<<endl; cout<<"sizeof(HowManyBytes):"<<sizeof(HowManyBytes)<<endl; cout<<endl; cout<<"offset of char a:"<<offsetof(HowManyBytes,a)<<endl; cout<<"offset of int b:"<<offsetof(HowManyBytes,b)<<endl; return 0; }
输出: sizeof(char):1 sizeof(int):4 sizeof(HowManyBytes):8 offset of char a:0 offset of int b:4
|
可以发现成员b并不是紧跟着成员a的,这是由于C/C++的int类型数据要求对齐到4字节,即要求int类型数据必须放在一个能够整除4的地址上,而char要求对齐到1字节。这就造成了成员a之后的3字节空间被空出, 通常我们也称因为对齐而造成的内存留空为填充数据(padding data)。在C++中,每个类型的数据除去长度等属性之外,都还有一项“被隐藏”属性,那就是对齐方式。对于每个内置或者自定义类型,都存在一 个特定的对齐方式。对齐方式通常是一个整数,它表示的是一个类型的对象存放的内存地址应满足的条件。
对齐的数据在读写上会有性能上的优势。比如频繁使用的数据如果与处理器的高速缓存器大小对齐,有可能提高缓存性能。而更为普遍 的,在一些平台上,不按照字对齐的数据会造成数据读取效率低下。
在C++语言中,我们可以通过sizeof 查询数据长度,但C++语言却没有对对齐方式有关的查询或者设定进行标准化,而语言本身又允许自定义类型、模板等诸多特性。编译器无法完全找到正确的对齐方式,这会在使用时造成困难。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| struct ColorVector { double r; double g; double b; double a; };
int main() {
cout << "alignof(ColorVector):" << alignof(ColorVector) << endl; return 1; }
输出:alignof(ColorVector):8
|
我们可以看到 ColorVecto 依然是对齐到8字节的地址边界上。为了能够高效地读写 ColorVector 大小的数据,我们最好能将其对齐在32字节的地址边界上。我们利用C++11新提供的修饰符alignas 来重新设定ColorVector的对齐方式。
1 2 3 4 5 6 7 8 9 10 11 12
| struct alignas(32)ColorVector{ double r; double g; double b; double a; }; int main(){
cout<<"alignof(ColorVector):"<<alignof(ColorVector)<<endl; return 1; } 输出:alignof(ColorVector):32
|
指定数据ColorVector对齐到32字节的地址边界上,只需要声明alignas(32)即可
c++11的 alignof 和 alignas
C++11在新标准中为了支持对齐,主要引入两个关键字:操作符 alignof、对齐描述符(alignment-specifier) alignas。
alignof:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| using namespace std; class InComplete; struct Completed { };
int main() { int a; long long b; auto &c = b; char d[1024]; cout << alignof(int) << endl << alignof(Completed) << endl; cout << alignof(a) << endl << alignof(b) << endl << alignof(c) << endl << alignof(d) << endl; }
|
alignas:
alignas 既可以接受常量表达式,也可以接受类型作为参数
1
| alignas(double) char c; <==> alignas(alignof(double)) char c;
|
我们在使用常量表达式作为 alignas 的操作符的时候,其结果必须是以2 的自然数幂次作为对齐值。
对齐描述符可以作用于各种数据。具体来说,可以修饰变量、类的数据成员等,而位域(bit field),函数以及用register声明的变量则不可以。
采用了模板的方式来实现一个固定容量但是大小随着所用的数据类型变化的容器类型
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 27 28 29 30 31 32 33 34 35
| struct alignas(alignof(double) * 4)ColorVector { double r; double g; double b; double a; };
template<typename T> class FixedCapacityArray { public: void push_back(T t) {}
char alignas(T) data[1024] = {0}; };
int main() { FixedCapacityArray<char> arrCh; cout << "alignof(char):" << alignof(char) << endl; cout << "alignof(arrCh.data):" << alignof(arrCh.data) << endl; FixedCapacityArray<ColorVector> arrCV; cout << "alignof(ColorVector):" << alignof(ColorVector) << endl; cout << "alignof(arrCV.data):" << alignof(arrCV.data) << endl; return 1; }
输出: alignof(char):1 alignof(arrCh.data):1 alignof(ColorVector):32 alignof(arrCV.data):1
|
FixedCapacityArray固定 使用1024字节的空间,但由于模板的存在,可以实例化为各种版本。这样一来,我们可以在相同的内存使用量的前提下,做出多种类型 (内置或者自定义)版本的数组。
在STL 库中,还内建了std::align函数来动态地根据指定的对齐方式调整数据块的位置。函数原型:
1 2
| inline void* align(size_t __align, size_t __size, void*& __ptr, size_t& __space) noexcept
|
该函数在 ptr 指向的大小为 space 的内存中进行对齐方式的调整,将 ptr 开始的 size大小的数据调整为按 align 对齐。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| #include <iostream> #include <memory> using namespace std; struct ColorVector { double r; double g; double b; double a; }; int main() { size_t const size = 100; ColorVector *const vec = new ColorVector[size]; void *p = vec; size_t sz = size; void *aligned = align(alignof(double) * 4, size, p, sz); if (aligned != nullptr) cout << alignof(p) << endl; }
|
c++11还提供了aligned_storage 及 aligned_union,函数原型:
1 2 3 4 5
| template<std::size_t Len,std::size_t Align=> struct aligned_storage;
template<std::size_t Len,class...Types> struct aligned_union;
|
还没有理解aligned_storage 及 aligned_union 的正确使用。
参考:书籍《深入理解c++11》
内存对齐概念:https://www.cnblogs.com/zhao-zongsheng/p/9099603.html