在C++编程中,内存对齐是一个重要的概念,它关乎于数据在内存中如何布局以提高访问效率。C++11标准引入了两个关键的特性来支持内存对齐:alignof
和alignas
。这两个特性提供了对内存对齐的直接控制,让开发者能够更好地优化程序性能。本文将深入介绍alignof
和alignas
的相关知识,帮助小白从入门到精通。
内存对齐是指数据在内存中的存储地址必须满足特定的对齐要求,通常是该类型大小的倍数。例如,int
类型通常对齐到4字节边界,double
类型通常对齐到8字节边界。内存对齐是一个整数,意味着该数据成员地址只能位于内存对齐的倍数上,而对齐之间的未使用空间被称为填充数据。
以下代码展示了内存对齐的现象:
#include
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; //0
cout << "offset of int b: " << offsetof(HowManyBytes, b) << endl; //4
return 0;
}
在上述代码中,成员a
占1个字节,成员b
占4字节,但结构体HowManyBytes
的大小为8字节,这是因为C/C++对数据结构有着对齐要求,b
的位置为4而不是1,a
之后的1、2、3三个字节为填充数据。
内存对齐主要有以下两点优势:
alignof
是一个操作符,用于查询类型或变量的对齐要求。它返回一个std::size_t
类型的值,表示类型或变量的对齐字节数。在C++11之前,对齐方式是无法得知的,只能自己判断,且不同的平台实现方式可能不同,而alignof
操作符可以让开发者在编译期确定某个数据类型的内存对齐要求。
alignof
的语法非常简单,其基本形式为:
alignof(type);
其中type
是要查询对齐要求的类型,可以是基本类型、结构体、类等。例如:
#include
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。
下面是更多关于alignof
的使用示例:
#include
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
可以准确地查询出不同类型的对齐要求。
alignof
获取定义完整类型的内存对齐要求,但不支持获取不完整类型或变量对齐值。例如:#include
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
cout << 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
是一个对齐说明符,用于指定变量或类型的最小对齐要求。alignas
可以用于变量声明或类型定义中,以确保所声明的变量或类型实例具有特定的对齐。它允许开发者显式指定类型或对象的对齐方式,而不是依赖于编译器的默认对齐方式。
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字节对齐的方式存储。
下面是一些关于alignas
的使用示例:
#include
// 每个 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
成功地指定了类型的对齐要求。
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
设置的对齐是否生效。例如:
#include
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
某些CPU架构对未对齐访问支持不好,强制对齐可以提升性能。在多媒体处理、科学计算和游戏开发等领域,正确的内存对齐可以显著提升数据处理速度。例如,在使用SIMD指令集时,需要将数据对齐到指定的字节边界,否则可能会导致性能下降。
不同平台的默认对齐可能不同,通过alignof
可以统一判断,使用alignas
可以确保在不同平台上都能满足特定的对齐要求。例如,在进行跨平台的数据传输时,为了保证数据的一致性和正确性,需要对数据进行统一的对齐处理。
分配内存时要考虑对齐,确保不同类型都能正确放置。在内存池设计中,使用alignas
可以保证分配的内存块满足特定的对齐要求,提高内存的使用效率。例如:
#include
#include
// 自定义内存池类
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
函数调整内存地址以满足对齐要求,确保分配的内存块是对齐的。
在与硬件直接交互的编程中,如驱动开发或嵌入式系统编程,内存对齐也是一个必须考虑的因素。例如,DMA(直接内存访问)或寄存器访问时通常有严格的对齐要求,使用alignas
可以确保数据满足硬件的对齐要求,避免出现访问错误。
alignof
和alignas
是C++11中非常有用的特性,它们为开发者提供了对内存对齐的直接控制。alignof
用于查询类型或变量的对齐要求,alignas
用于指定变量或类型的最小对齐要求。合理使用alignof
和alignas
可以提高程序的性能,特别是在需要高性能优化的代码中,如多媒体处理、科学计算和游戏开发等领域。同时,在跨平台开发、内存池设计和与硬件通信等场景中,alignof
和alignas
也能发挥重要作用。在使用alignof
和alignas
时,需要注意其语法规则和使用限制,以确保代码的正确性和可移植性。希望本文能够帮助你深入理解和掌握C++11中alignof
和alignas
的使用方法。