C++11中alignof和alignas的入门到精通指南

举报
码事漫谈 发表于 2025/06/18 19:37:54 2025/06/18
【摘要】 一、引言 二、内存对齐的概念和作用 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...

一、引言

在C++编程中,内存对齐是一个重要的概念,它关乎于数据在内存中如何布局以提高访问效率。C++11标准引入了两个关键的特性来支持内存对齐:alignofalignas。这两个特性提供了对内存对齐的直接控制,让开发者能够更好地优化程序性能。本文将深入介绍alignofalignas的相关知识,帮助小白从入门到精通。

二、内存对齐的概念和作用

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;	//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三个字节为填充数据。

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];
    // 对内置类型和完整类型使用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说明符

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的结合使用

alignofalignas可以结合使用,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可以确保数据满足硬件的对齐要求,避免出现访问错误。

七、总结

alignofalignas是C++11中非常有用的特性,它们为开发者提供了对内存对齐的直接控制。alignof用于查询类型或变量的对齐要求,alignas用于指定变量或类型的最小对齐要求。合理使用alignofalignas可以提高程序的性能,特别是在需要高性能优化的代码中,如多媒体处理、科学计算和游戏开发等领域。同时,在跨平台开发、内存池设计和与硬件通信等场景中,alignofalignas也能发挥重要作用。在使用alignofalignas时,需要注意其语法规则和使用限制,以确保代码的正确性和可移植性。希望本文能够帮助你深入理解和掌握C++11中alignofalignas的使用方法。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。