有关字节对齐的深入思考
2017-12-26 19:54
183 查看
前几天看到一篇博客,为什么鸟哥说 int 再怎么随机也申请不到奇数地址。
字节对齐基本是个常识,但是背后的原因值得好好思考。这篇博客里提到了:
需要字节对齐的根本原因在于CPU访问数据的效率问题。因为CPU每次都是从以4字节(32位CPU)或是8字节(64位CPU)的整数倍的内存地址中读进数据的。(更深入的原因,谁告知下),如果不对齐的话,很有可能一个4字节int需要分两次读取。
那么,我就接着在我这篇博客里,聊聊为什么CPU每次都是以4字节或者8字节的方式读取数据的,有不对的地方,还请指出。
内存的带宽等于
因此呢,现在市面上能买到的内存条,其数据总线的输出几乎都是多颗粒拼出来的,如普通台式机内存条的DIMM标准,其数据总线宽度为64位,要用8颗8bit颗粒或者4颗16bit颗粒来拼。不过,不用考虑这么多的细节,把它当作一个大号64bit位宽颗粒就可以。
有了内存条以后,大带宽的存储器实现了,直接挂在CPU上?这样是不行的。内存颗粒有个重要的特性叫做易失性,它需要定时进行刷新操作来维持内部电容上的电压。实现这部分逻辑的东西叫做内存控制器。
就从我知道的比较早的CPU开始说吧,8080,intel的产品,数据总线宽度为8位,也就是说,CPU一个时钟周期可以加载1字节的数据。后来,CPU处理能力越來越强,数据总线宽度逐渐扩大,8086的16位,到32位,64位等。然而软件代码里的地址的单位却一直都是字节,这也为后面字节对齐埋下伏笔。
比如一个4Gb(注意是比特不是字节)的内存,其总的存储容量为2^32bit。
如果按照代码中8bit一个地址的话,那么地址总线的宽度为32-3=29bit,总的存储容量就是2^29 * 8bit = 1024 * 1024 * 512 Byte = 512MByte.
如果按照内存输出32bit来计算的话,内存接口提供的地址总线宽度只有32-5=27bit,总的存储容量守恒,仍然是512MByte,只是计算方法变成了2^27 * 32bit = 1024 * 1024 * 128 * 4Byte = 512MByte.
那么在实际访问时,比如访问0x0001位置,这个地址经过内存控制器转换后,实际传递给内存的地址是0x0000,出现在数据总线上的数据是0x61626364。内存只可能一次性送出其数据总线宽度的数据,大于或者小于都是不行的。说白了,代码的层面只访问1个字节,内存则一次性给出4字节的内容,要哪个字节,交给上面去判断。
进一步推断,如果代码里发生了四次读取操作,分别是0x0000, 0x0001, 0x0002, 0x0003,那么,内存的数据总线和地址总线都不会发生任何变化。直到读取0x0004位置时,内存的地址总线才会切换到0x0001,并送出0x12345678。
字节对齐基本是个常识,但是背后的原因值得好好思考。这篇博客里提到了:
需要字节对齐的根本原因在于CPU访问数据的效率问题。因为CPU每次都是从以4字节(32位CPU)或是8字节(64位CPU)的整数倍的内存地址中读进数据的。(更深入的原因,谁告知下),如果不对齐的话,很有可能一个4字节int需要分两次读取。
那么,我就接着在我这篇博客里,聊聊为什么CPU每次都是以4字节或者8字节的方式读取数据的,有不对的地方,还请指出。
内存结构
内存的准确称呼应该叫做DRAM,动态随机存储器,它依靠其内部电容的电荷来记录信息,具有容量大、速度快的特点。内存的基本单元,是内存条上的一个个黑色颗粒。颗粒本身的数据输出都是8bit或者16bit,远远达不到现代电脑的吞吐量要求。内存的带宽等于
时钟频率x数据总线宽度,提高任一个都可以提高内存的带宽。前者的提升可以带来带宽的增大,但负面效果也很明显,一是功耗会急剧增大,二是硬件设计难度会增大很多,对时序的要求会更加严格。而提高数据总线宽度相对来说就好很多,可以通过叠加颗粒的方式实现。比如要实现一个32bit的输出,只需要用两颗16bit的颗粒,一颗提供D[0:15],一颗提供D[16:31]。当然,实际操作起来肯定不是把俩颗粒拼起来这么简单,还要考虑到更多复杂的因素,这里略过不谈。
因此呢,现在市面上能买到的内存条,其数据总线的输出几乎都是多颗粒拼出来的,如普通台式机内存条的DIMM标准,其数据总线宽度为64位,要用8颗8bit颗粒或者4颗16bit颗粒来拼。不过,不用考虑这么多的细节,把它当作一个大号64bit位宽颗粒就可以。
有了内存条以后,大带宽的存储器实现了,直接挂在CPU上?这样是不行的。内存颗粒有个重要的特性叫做易失性,它需要定时进行刷新操作来维持内部电容上的电压。实现这部分逻辑的东西叫做内存控制器。
CPU数据总线宽度的变迁
数据总线宽度代表了CPU一次能读取的数据大小。就从我知道的比较早的CPU开始说吧,8080,intel的产品,数据总线宽度为8位,也就是说,CPU一个时钟周期可以加载1字节的数据。后来,CPU处理能力越來越强,数据总线宽度逐渐扩大,8086的16位,到32位,64位等。然而软件代码里的地址的单位却一直都是字节,这也为后面字节对齐埋下伏笔。
数据宽度的差异
前面提到了,软件代码里至今地址的单位一直都是字节,但在底层硬件,早已不是这样了。假设一个32bit位宽的存储器,当地址总线上出现的是0x0000时,数据总线上出现的是0~31bit的内容。当地址总线上出现的是0x0001时,数据总线上的内容已经变成了32~63bit的内容。可以看到,硬件为了最大限度的利用数据总线,并不是按照字节来存储的,而是按照实际位宽去存储的。节约地址总线的宽度,就意味着更少的引脚、更低的功耗。比如一个4Gb(注意是比特不是字节)的内存,其总的存储容量为2^32bit。
如果按照代码中8bit一个地址的话,那么地址总线的宽度为32-3=29bit,总的存储容量就是2^29 * 8bit = 1024 * 1024 * 512 Byte = 512MByte.
如果按照内存输出32bit来计算的话,内存接口提供的地址总线宽度只有32-5=27bit,总的存储容量守恒,仍然是512MByte,只是计算方法变成了2^27 * 32bit = 1024 * 1024 * 128 * 4Byte = 512MByte.
举个例子
假设有一段内存dump如下, 四个char类型:abcd和一个int类型的0x12345678。假设内存的位宽是32bit。0x0000 'a' 0x0001 'b' 0x0002 'c' 0x0003 'd' 0x0004 0x78 0x0005 0x56 0x0006 0x34 0x0007 0x12 ...
那么在实际访问时,比如访问0x0001位置,这个地址经过内存控制器转换后,实际传递给内存的地址是0x0000,出现在数据总线上的数据是0x61626364。内存只可能一次性送出其数据总线宽度的数据,大于或者小于都是不行的。说白了,代码的层面只访问1个字节,内存则一次性给出4字节的内容,要哪个字节,交给上面去判断。
进一步推断,如果代码里发生了四次读取操作,分别是0x0000, 0x0001, 0x0002, 0x0003,那么,内存的数据总线和地址总线都不会发生任何变化。直到读取0x0004位置时,内存的地址总线才会切换到0x0001,并送出0x12345678。
相关文章推荐
- 有关字节对齐的介绍
- 惭愧,直到今天才对“数据类型字节对齐”有个深入的了解,对以前读书时学习的知识没有深刻理解啊 C/C++学习 C/C++学习 漫漫人生,澎湃的经历不断促使改变自己,永恒不变的是一颗骄傲的心!
- 关于Malloc字节对齐的思考
- 有关结构体字节对齐方式
- 顶级c程序员之路 选学篇-1 深入理解字节,字节序与字节对齐
- C语言深入学习系列 - 字节对齐&内存管理
- 有关字节对齐
- 深入研究字节对齐问题
- 你会用sizeof吗?(vc篇)--有关“字节对齐”
- C语言深入学习系列 - 字节对齐&内存管理
- 深入理解C语言字节对齐
- 深入研究字节对齐问题 .
- 有关字节对齐的问题
- 关于字节对齐,和程序优化有关
- 有关字节对齐的知识汇总
- ARM程序由于字节对齐引起的问题深入分析[转]
- 有关阳光动力2号飞越太平洋的深入思考
- 手工实现字节对齐 及 代码质量思考
- C语言深入学习系列 - 字节对齐&内存管理(怎么会有这么好的资料呢 兴奋。。。。)
- ARM程序由于字节对齐引起的问题深入分析