文章目录
- 一、引言
- 二、内存对齐的概念和作用
- 2.1 什么是内存对齐
- 2.2 内存对齐的优势
- 三、alignof运算符
- 3.1 定义和作用
- 3.2 语法规则
- 3.3 使用示例
- 3.4 注意事项
- 四、alignas说明符
- 4.1 定义和作用
- 4.2 语法规则
- 4.3 使用示例
- 4.4 注意事项
- 五、alignof和alignas的结合使用
- 六、实际应用场景
- 6.1 性能优化
- 6.2 跨平台开发
- 6.3 内存池设计
- 6.4 与硬件通信
- 七、总结
一、引言
在C++编程中,内存对齐是一个重要的概念,它关乎于数据在内存中如何布局以提高访问效率。C++11标准引入了两个关键的特性来支持内存对齐:alignof
和alignas
。这两个特性提供了对内存对齐的直接控制,让开发者能够更好地优化程序性能。本文将深入介绍alignof
和alignas
的相关知识,帮助小白从入门到精通。
二、内存对齐的概念和作用
2.1 什么是内存对齐
内存对齐是指数据在内存中的存储地址必须满足特定的对齐要求,通常是该类型大小的倍数。例如,int
类型通常对齐到4字节边界,double
类型通常对齐到8字节边界。内存对齐是一个整数,意味着该数据成员地址只能位于内存对齐的倍数上,而对齐之间的未使用空间被称为填充数据。
以下代码展示了内存对齐的现象:
#include <iostream>
using namespace std;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; //0cout << "offset of int b: " << offsetof(HowManyBytes, b) << endl; //4return 0;
}
在上述代码中,成员a
占1个字节,成员b
占4字节,但结构体HowManyBytes
的大小为8字节,这是因为C/C++对数据结构有着对齐要求,b
的位置为4而不是1,a
之后的1、2、3三个字节为填充数据。
2.2 内存对齐的优势
内存对齐主要有以下两点优势:
- 跨平台:有些平台要求内存对齐,否则程序无法运行。不同硬件平台对存储空间的处理上存在很大的不同,某些平台对特定类型的数据只能从特定地址开始存取,而不允许其在内存中任意存放。例如Motorola 68000处理器不允许16位的字存放在奇地址,否则会触发异常,因此在这种架构下编程必须保证字节对齐。
- 性能:内存对齐有利于提高数据缓存速度。尽管内存是以字节为单位,但是大部分处理器并不是按字节块来存取内存的,它一般会以双字节、四字节、8字节、16字节甚至32字节为单位来存取内存,我们将上述这些存取单位称为内存存取粒度。假如没有内存对齐机制,数据可以任意存放,处理器在读取数据时可能需要进行多次内存访问才能获取完整的数据,这会显著降低性能。而合理的内存对齐可以减少CPU的内存访问开销,提高程序的运行效率。
三、alignof运算符
3.1 定义和作用
alignof
是一个操作符,用于查询类型或变量的对齐要求。它返回一个std::size_t
类型的值,表示类型或变量的对齐字节数。在C++11之前,对齐方式是无法得知的,只能自己判断,且不同的平台实现方式可能不同,而alignof
操作符可以让开发者在编译期确定某个数据类型的内存对齐要求。
3.2 语法规则
alignof
的语法非常简单,其基本形式为:
alignof(type);
其中type
是要查询对齐要求的类型,可以是基本类型、结构体、类等。例如:
#include <iostream>struct MyStruct {char c;int i;
};int main() {std::cout << "Alignment of char: " << alignof(char) << std::endl;std::cout << "Alignment of int: " << alignof(int) << std::endl;std::cout << "Alignment of MyStruct: " << alignof(MyStruct) << std::endl;return 0;
}
在上述代码中,alignof(char)
返回char
类型的对齐字节数,通常为1;alignof(int)
返回int
类型的对齐字节数,通常为4;alignof(MyStruct)
返回结构体MyStruct
的对齐字节数,取决于结构体中最大对齐要求的成员,这里为4。
3.3 使用示例
下面是更多关于alignof
的使用示例:
#include <iostream>struct Foo {int i;float f;char c;
};struct Empty {};struct alignas(64) Empty64 {};int main() {std::cout << "Alignment of" "\n""- char : " << alignof(char) << "\n""- pointer : " << alignof(int*) << "\n""- class Foo : " << alignof(Foo) << "\n""- empty class : " << alignof(Empty) << "\n""- alignas(64) Empty: " << alignof(Empty64) << "\n";return 0;
}
运行上述代码,输出结果如下:
Alignment of
- char : 1
- pointer : 8
- class Foo : 4
- empty class : 1
- alignas(64) Empty: 64
从输出结果可以看出,alignof
可以准确地查询出不同类型的对齐要求。
3.4 注意事项
- 不支持获取不完整类型或变量对齐值:C++11支持操作符
alignof
获取定义完整类型的内存对齐要求,但不支持获取不完整类型或变量对齐值。例如:
#include <iostream>
using namespace std;class InComplete;
struct Completed{};int main() {int a;long long b;auto & c = b;char d[1024];// 对内置类型和完整类型使用alignofcout << alignof(int) << endl; // 4cout << alignof(Completed) << endl; // 1// 对变量、引用或者数组使用alignof,以下代码无法编译// cout << alignof(a) << endl;// cout << alignof(b) << endl;// cout << alignof(c) << endl;// cout << alignof(d) << endl;// 本句无法通过编译,Incomplete类型不完整// cout << alignof(InComplete) << endl;return 0;
}
- 跨平台差异:不同编译器和不同平台对基本类型的默认对齐要求可能略有不同,因此使用
alignof
时需要注意平台兼容性问题。
四、alignas说明符
4.1 定义和作用
alignas
是一个对齐说明符,用于指定变量或类型的最小对齐要求。alignas
可以用于变量声明或类型定义中,以确保所声明的变量或类型实例具有特定的对齐。它允许开发者显式指定类型或对象的对齐方式,而不是依赖于编译器的默认对齐方式。
4.2 语法规则
alignas
的语法如下:
alignas(alignment) type variable;
其中alignment
是一个整数或常量表达式,表示字节对齐数,type
是声明的类型,variable
是变量。alignment
必须是求值为零或合法的对齐或扩展对齐的整型常量表达式,且通常为2的幂次方(如1、2、4、8、16等)。例如:
struct alignas(16) MyStruct {int x;float y;
};
在上述代码中,MyStruct
被指定为16字节对齐,即每个MyStruct
类型的对象都必须在内存中以16字节对齐的方式存储。
4.3 使用示例
下面是一些关于alignas
的使用示例:
#include <iostream>// 每个 sse_t 类型的对象将会按照 32 字节的边界对齐:
struct alignas(32) sse_t {float sse_data[4];
};// 数组 cacheline 将会按照 64 字节的边界对齐:
using cacheline_t = alignas(64) char[64];
cacheline_t cacheline;int main() {sse_t x;std::cout << "Alignment of sse_t: " << alignof(sse_t) << std::endl;std::cout << "Address of x: " << &x << std::endl;std::cout << "Alignment of cacheline_t: " << alignof(cacheline_t) << std::endl;std::cout << "Address of cacheline: " << &cacheline << std::endl;return 0;
}
运行上述代码,输出结果如下:
Alignment of sse_t: 32
Address of x: 0x7ffef1f24c40
Alignment of cacheline_t: 64
Address of cacheline: 0x7ffef1f24c80
从输出结果可以看出,x
的地址是以32字节对齐的,cacheline
的地址是以64字节对齐的,说明alignas
成功地指定了类型的对齐要求。
4.4 注意事项
- 表达式要求:对于
alignas(expression)
,表达式必须是0或幂为2(1、2、4、8、16、…)的整型常量表达式。所有其他表达式的格式不正确,要么会被编译器忽略掉。 - 不能修饰的对象:
alignas
不能应用于函数形参或catch
子句的异常形参。例如:
alignas(double) void f(); // 错误:alignas不能修饰函数
- 对齐要求不能削弱自然对齐:如果某个声明上的最严格(最大)
alignas
比当它没有任何alignas
说明符的情况下本应有的对齐更弱(即弱于其原生对齐,或弱于同一对象或类型的另一声明上的alignas
),那么程序非良构。例如:
struct alignas(8) S {};
struct alignas(1) U { S s; }; // 错误:如果没有 alignas(1) 那么 U 的对齐将会是 8
- 无效的非零对齐:无效的非零对齐,例如
alignas(3)
是非良构的。同一声明上,比其他alignas
弱的有效的非零对齐被忽略,始终忽略alignas(0)
。
五、alignof和alignas的结合使用
alignof
和alignas
可以结合使用,alignof
可以用来验证alignas
设置的对齐是否生效。例如:
#include <iostream>struct alignas(16) MyStruct {int x;double y;
};int main() {std::cout << "alignof(MyStruct): " << alignof(MyStruct) << std::endl;return 0;
}
在上述代码中,MyStruct
被指定为16字节对齐,通过alignof(MyStruct)
可以验证其对齐要求确实为16字节。运行上述代码,输出结果如下:
alignof(MyStruct): 16
六、实际应用场景
6.1 性能优化
某些CPU架构对未对齐访问支持不好,强制对齐可以提升性能。在多媒体处理、科学计算和游戏开发等领域,正确的内存对齐可以显著提升数据处理速度。例如,在使用SIMD指令集时,需要将数据对齐到指定的字节边界,否则可能会导致性能下降。
6.2 跨平台开发
不同平台的默认对齐可能不同,通过alignof
可以统一判断,使用alignas
可以确保在不同平台上都能满足特定的对齐要求。例如,在进行跨平台的数据传输时,为了保证数据的一致性和正确性,需要对数据进行统一的对齐处理。
6.3 内存池设计
分配内存时要考虑对齐,确保不同类型都能正确放置。在内存池设计中,使用alignas
可以保证分配的内存块满足特定的对齐要求,提高内存的使用效率。例如:
#include <iostream>
#include <cstddef>// 自定义内存池类
class MemoryPool {
public:MemoryPool(std::size_t blockSize, std::size_t align) : blockSize_(blockSize), align_(align) {// 分配内存pool_ = new char[blockSize_];// 调整内存地址以满足对齐要求char* alignedPool = reinterpret_cast<char*>(std::align(align_, blockSize_, pool_, blockSize_));if (!alignedPool) {throw std::bad_alloc();}current_ = alignedPool;}~MemoryPool() {delete[] pool_;}void* allocate(std::size_t size) {if (current_ + size <= pool_ + blockSize_) {void* result = current_;current_ += size;return result;}return nullptr;}private:char* pool_;char* current_;std::size_t blockSize_;std::size_t align_;
};int main() {// 创建一个1024字节、16字节对齐的内存池MemoryPool pool(1024, 16);// 从内存池中分配一个32字节的内存块void* ptr = pool.allocate(32);if (ptr) {std::cout << "Allocated memory address: " << ptr << std::endl;} else {std::cout << "Memory allocation failed." << std::endl;}return 0;
}
在上述代码中,MemoryPool
类用于管理一个内存池,通过std::align
函数调整内存地址以满足对齐要求,确保分配的内存块是对齐的。
6.4 与硬件通信
在与硬件直接交互的编程中,如驱动开发或嵌入式系统编程,内存对齐也是一个必须考虑的因素。例如,DMA(直接内存访问)或寄存器访问时通常有严格的对齐要求,使用alignas
可以确保数据满足硬件的对齐要求,避免出现访问错误。
七、总结
alignof
和alignas
是C++11中非常有用的特性,它们为开发者提供了对内存对齐的直接控制。alignof
用于查询类型或变量的对齐要求,alignas
用于指定变量或类型的最小对齐要求。合理使用alignof
和alignas
可以提高程序的性能,特别是在需要高性能优化的代码中,如多媒体处理、科学计算和游戏开发等领域。同时,在跨平台开发、内存池设计和与硬件通信等场景中,alignof
和alignas
也能发挥重要作用。在使用alignof
和alignas
时,需要注意其语法规则和使用限制,以确保代码的正确性和可移植性。希望本文能够帮助你深入理解和掌握C++11中alignof
和alignas
的使用方法。