对齐支持

对齐支持

数据对齐

回忆一个经典案例:

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;
}
/*
#define offsetof(type,member) //不能以标准实现,必须由编译器实现
宏 offsetof 会展开为 std::size_t 类型的整数常量表达式,它的值是从指定类型对象开始到它指定的子对象的字节数偏移,并包括可能有的填充位。
如果type不是POD类型,那么offsetof的结果是未定义的
*/

输出:
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() {
//使用C++11中的alignof来查询ColorVector的对齐方式
cout << "alignof(ColorVector):" << alignof(ColorVector) << endl;
return 1;
}

/*
alignof运算符:alignof(类型标识符) 返回size_t类型值
返回由类型标识所指示的类型的任何实例所要求的对齐字节数,该类型可以是完整对象类型、元素类型完整的数组类型或者到这些类型之一的引用类型。
如果类型是引用类型,那么运算符返回被引用类型的对齐要求;如果类型是数组类型,那么返回元素类型的对齐要求。
*/
输出: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(){
//使用C++11中的alignof来查询ColorVector的对齐方式
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];
//对内置类型和完整类型使用alignof
cout << alignof(int) << endl//4
<< alignof(Completed) << endl;//1
//对变量、引用或者数组使用alignof
cout << alignof(a) << endl//4
<< alignof(b) << endl//8
<< alignof(c) << endl//8,与b相同
<< alignof(d) << endl;//1,与元素要求相同
//cout<<alignof(Incomplete)<<endl; 本句无法通过编译,Incomplete类型不完整
}

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) {/*在data中加入t变量*/}

//...
//一些其他成员函数、成员变量等
//...
char alignas(T) data[1024] = {0}; //根据模板参数类型指定对齐方式,且控制数组大小为1024字节
//对应的数组元素个数为:length=1024/sizeof(T);
};

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);
//在指针p指向的空间大小为sz的内存中,将大小为size的内存以alignof(double)*4的方式对齐
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=/*default-alignment*/
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