很多写C/C++的人都知道“内存对齐”的概念以及规则,但不一定对他有很深入的了解。小编试着从硬件到C++语言、更彻底地讲一下C++的内存对齐。
什么是内存对齐(memory alignment)
首先,什么是内存对齐(memory alignment)?这个是从硬件层面出现的概念。大家都知道,可执行程序是由一系列CPU指令构成的。CPU指令中有一些指令是需要访问内存的。最常见的就是“从内存读到寄存器”,以及“从寄存器写到内存”。在老的架构中(包括x86),也有一些运算的指令是可以直接以内存为操作数,那么这些指令也隐含了内存的读取。在很多CPU架构下,这些指令都要求操作的内存地址(更准确的说,操作内存的起始地址)能够被操作的内存大小整除,满足这个要求的内存访问叫做访问对齐的内存(aligned memory access),否则就是访问未对齐的内存(unaligned memory access)。举例来说,ARM的LDRH指令从内存中读取2个byte到寄存器中。如果指定的内存的地址是0x2587c20,因为0x2587c20这个数能够被2整除,所以这2个byte是对齐的。而如果指定的内存的地址是0x2587c33,因为不能被2整除,所以是未对齐的。
那如果访问未对齐的内存会出现什么结果呢?这个要看CPU。
有些CPU架构可以访问未对齐的内存,但是会有性能上的影响。典型的就是x86架构CPU
有些CPU会抛出异常
还有些CPU不会抛出任何异常,会静默地访问错误的地址
近几年也有些CPU的一部分指令可以正常访问未对齐的内存,同时不会有性能影响
因为每个CPU对未对齐内存的访问的处理方式都不一样,所以访问未对齐的内存是要尽量避免的。所以就出现了C/C++的内存对齐机制。
C++的内存对齐机制
在C++中每个类型都有两个属性,一个是大小(size),还有一个就是对齐要求(alignment requirement),或称之为对齐量(alignment)。C++标准并没有规定每个类型的对齐量,但是一般都会有这样的规律。
所有基础类型的对齐量等于这个类型的大小。
struct, class, union类型的对齐量等于他的非静态成员变量中最大的对齐量。
另外,标准规定所有的对齐量必须是2的幂。
编译器在给一个变量分配内存时,都要算出并满足这个类型的对齐要求。struct和class类型的非静态成员变量的字节数偏移(offset)也要满足各自类型的对齐要求。
举例来说,
复制代码
class MyObject
{
char c;
int i;
short s;
};
复制代码
c是char类型,对齐要求是1,i是int类型,对齐要求是4,s是short类型,对齐要求是2。那么MyObject取最大的,也就是4作为他的对齐要求。如果在某个函数中声明了MyObject类型的变量,那么分配给这个变量的内存的起始地址是能够被4整除的。
我们再看MyObject的成员变量。c是MyObject的第一个成员变量,所以他的字节数偏移是0,也就是说变量c占据MyObject的第一个byte。i的对齐要求是4,所以字节数偏移必须是4的倍数,又因为变量i必须在变量c的后面,于是i的字节数偏移就是4,也就是说变量i占据MyObject的第5到第8个byte,而第2到第4个byte则是空白填充(padding)。s的对齐要求是2,又因为s必须在i的后面,所以s的字节数偏移是8,也就是说,变量s占据MyObject的第9个和第10个byte。另外,因为struct、class、union类型的数组的每个元素都要内存对齐,所以一般来说struct、class、union的大小都是这个类型的对齐量的整数倍,所以MyObject的大小是12,也就是说,变量s后面会有2个byte的空白填充。
因为C++中所有内存访问都是通过变量的读写来访问的,这个机制确保了所有变量都满足了内存对齐,也就确保了程序中所有内存访问都是对齐的。
当然,C++不会阻止我们去访问未对齐的内存。例如,以下的代码就很可能会访问未对齐的内存:
char buf[10];
int* ptr = (int*)(buf + 1);
++*ptr;
这类代码是我们在实际工作中也是能遇到的。事实上这种写法是比较危险的,因为他很可能会去访问未对齐的内存。这也是为什么写c++大家都不推荐用c风格的类型转换写法,而是要用static_cast, dynamic_cast, const_cast与reinterpret_cast。这样的话,上面的代码就必须要使用reinterpret_cast,大家都知道reinterpret_cast是很危险的,也许就会想办法避免这样的逻辑。
常见CPU的未对齐内存访问
根据Intel最新的Intel 64及IA-32架构说明书,Intel 64及IA-32架构都支持未对齐内存的访问,但是会有性能上的额外开销(详见http://www.intel.com/products/processor/manuals)。但是实际上最近的Core系列CPU已经可以无额外开销访问未对齐的内存。
而手机上最常见的ARMv8架构,如果是普通的、不做多核同步的未对齐的内存访问,那么CPU可能会产生对齐错误(alignment fault)或者执行未对齐内存操作。换句话说,到底会报错还是正常执行,是要看具体CPU的实现的。即使是执行正常操作,也会有一些限制。例如,不能保证读写的原子性(操作一个byte的除外),很可能产生额外的开销等。ARMv8中的Cortex-A系列是手机上常见的CPU家族,他们就可以正常处理未对齐内存访问,但是一般会有额外的开销。
我们也可以写一个简单的程序测试一下自己的CPU对未对齐内存访问的支持,以下是代码:
复制代码
#include <iostream>
#include <chrono>
using namespace std;
using namespace std::chrono;
milliseconds test_duration(volatile int * ptr) // 使用volatile指针防止编译器的优化
{
auto start = steady_clock::now();
for (unsigned i = 0; i < 100'000'000; ++i)
{
++(*ptr);
}
auto end = steady_clock::now();
return duration_cast<milliseconds>(end - start);
}
int main()
{
int raw[2] = {0, 0};
{
int* ptr = raw;
cout << "address of aligned pointer: " << (void*)ptr << endl;
cout << "aligned access: " << test_duration(ptr).count() << "ms" << endl;
*ptr = 0;
}
{
int* ptr = (int*)(((char*)raw) + 1);
cout << "address of unaligned pointer: " << (void*)ptr << endl;
cout << "unaligned access: " << test_duration(ptr).count() << "ms" << endl;
*ptr = 0;
}
cin.get();
return 0;
}
复制代码
我测试使用的电脑的CPU是Intel Core i7 2630QM,是intel 2代酷睿CPU,测试结果为:
address of aligned pointer: 000000668DEFFA78
aligned access: 282ms
address of unaligned pointer: 000000668DEFFA79
unaligned access: 285ms
可以看出对齐与未对齐的内存访问没有性能上的差别。
在C++中修改对齐要求
一般情况下,我们不需要自定义对齐要求,但也会有很特殊的情况下需要做调整。C++中,我们可以使用alignas关键字修改一个类型、或者一个变量的对齐要求。例如:
复制代码
class MyObject
{
char c;
alignas(8) int i;
short s;
};
复制代码
这样的话,变量i的对齐要求由原本的4变成了8,结果就是,i的字节数偏移由4变成了8,s的字节数偏移由8变成了12,MyObject的对齐要求也变成了8,大小变成了16。
我们也可以对MyObject的定义使用alignas:
复制代码
class alignas(16) MyObject
{
char c;
int i;
short s;
};
复制代码

还可以在alignas里面写某个类型。也可以使用多个alignas,结果就是使用最大的对齐要求。例如以下MyObject的对齐要求就是16:

图1
复制代码
class alignas(int) alignas(16) MyObject
{
char c;
int i;
short s;
};
复制代码
alignas有一个限制,那就是不能用alignas改小对齐要求。例如以下的代码会报错:
alignas(1) int i;
另外,C++中,有一个特殊的类型:max_align_t,所有不大于他的对齐量叫做基础对齐量(fundamental alignment),比这个对齐量大的叫做扩展对齐量(extended alignment )。C++标准规定,所有平台必须要支持基础对齐量,而对于扩展对齐量的支持要看各个平台。一般来说max_align_t的对齐量等于long double的对齐量。
C++关于内存对齐的支持还有很多功能,例如查询对齐量的alignof关键字,可以创建任意大小任意对齐要求的类型的aligned_storage模板,还有方便模板编程的alignment_of等等,在此就不细述了。
最后你觉得我们的文章对你有帮助,欢迎关注我,可以私信我:久伴,领取学习资料,在评论下方可以关注我的学习群,你可以随时在上面向我们提问,把你在学习C++过程中所遇到的问题发给我们。我们每天都会按时回复大家的每一个问题,希望久伴可以伴随你从入门到专家。

从硬件到语言,详解C++的内存对齐相关推荐

  1. 如何用c语言编写stm32的程序吗,STM32入门C语言详解

    <STM32入门C语言详解>由会员分享,可在线阅读,更多相关<STM32入门C语言详解(6页珍藏版)>请在人人文库网上搜索. 1.最新 料推荐阅读 flash : 芯片内部存储 ...

  2. Drools 规则语言详解(上)

    http://www.blogjava.net/guangnian0412/archive/2006/06/09/51574.html http://www.blogjava.net/guangnia ...

  3. 克鲁斯卡尔算法c语言,Kruskal算法(一)之 C语言详解

    最小生成树 在含有n个顶点的连通图中选择n-1条边,构成一棵极小连通子图,并使该连通子图中n-1条边上权值之和达到最小,则称其为连通网的最小生成树. 例如,对于如上图G4所示的连通网可以有多棵权值总和 ...

  4. c++指针详解_c语言详解sizeof

    一.sizeof的概念 sizeof是C语言的一种单目操作符,如C语言的其他操作符++.--等. 它并不是函数. sizeof操作符以字节形式给出了其操作数的存储大小. 操作数可以是一个表达式或括在括 ...

  5. 排座系统c语言,2008noip排座位C语言详解.doc

    2008noip排座位C语言详解 2008noip排座位C语言详解 2.排座椅 (seat.pas/c/cpp)D对同学上课时会交头接耳.同学们在教室中坐成了M行N列,坐在第i行第j列 的同学的位置是 ...

  6. 有向图邻接矩阵c语言编程,邻接矩阵有向图(一)之 C语言详解

    本章介绍邻接矩阵有向图.在"图的理论基础"中已经对图进行了理论介绍,这里就不再对图的概念进行重复说明了.和以往一样,本文会先给出C语言的实现:后续再分别给出C++和Java版本的实 ...

  7. 邻接矩阵用c语言,邻接矩阵无向图(一)之 C语言详解

    本章介绍邻接矩阵无向图.在"图的理论基础"中已经对图进行了理论介绍,这里就不再对图的概念进行重复说明了.和以往一样,本文会先给出C语言的实现:后续再分别给出C++和Java版本的实 ...

  8. 顺序栈基本操作(入栈和出栈)C语言详解

    #include <stdio.h> #include <stdlib.h> /*顺序栈基本操作(入栈和出栈)C语言详解栈的具体实现(1)顺序栈(2)链栈栈的应用(1)回退 ( ...

  9. c语言霍夫曼函数,使用C语言详解霍夫曼树数据结构

    1.基本概念 a.路径和路径长度 若在一棵树中存在着一个结点序列 k1,k2,--,kj, 使得 ki是ki+1 的双亲(1<=i 从 k1 到 kj 所经过的分支数称为这两点之间的路径长度,它 ...

最新文章

  1. Hadoop的存储架构介绍
  2. odoo tree视图过滤数据_在EA中建立优雅的仪表板视图
  3. 如何理解Android中的xmlns
  4. vc怎么查看c源码的汇编语言,VC++代码的汇编分析(一)
  5. Android之category
  6. Git Pull Failed Your local changes would be overwritten by merge.(git报错)
  7. 在线图片坐标拾取工具
  8. NAT、远程访问和站点间***集成
  9. 【深入理解JVM笔记】什么是元数据?
  10. [转载] Python 完整实现的简单遗传算法(SGA)
  11. Tableau 2018.3破解教程(截止2019-08-18)试用过期也可用
  12. sm3 算法java_java sm3加密算法
  13. Python爬虫,超简单地实现一键提取阴阳师原画
  14. CentOS安装Mingw32
  15. 小米android怎么刷机教程,安卓刷机教程_小米刷机教程_手机刷机教程-IT资讯(PC6.com)...
  16. 数据库中的日期相减_sql日期相减得到天数【sql日期时间相减语句】
  17. 他们的爱情 王小波和李银河
  18. 倍福PLC——ADS上位机通讯
  19. PHP事务数据库写法,PHP 操作 MySQL 执行数据库事务
  20. 吉利闯入“工业互联网”,李书福的“微笑曲线”能否如愿?

热门文章

  1. java毕业设计牙科诊所管理系统Mybatis+系统+数据库+调试部署
  2. shell脚本编写时的必备命令(文章末尾含几个简单应用的脚本实例)
  3. matlab中单独存图_奇怪的Matlab画图技巧系列1–保存高清大图
  4. python里defoults_Python Part.Compound方法代码示例
  5. RabbitMQ-客户端源码之ChannelN
  6. 通讯录姓氏多音字的问题解决
  7. EPP协议(RFC5730翻译)
  8. Java使用JDBC调用Mysql函数和存储过程
  9. 【Linux练习生】进程间通信
  10. 全功能Python测试框架:pytest