到底为什么要进行字节对齐?

JZY4077 2010-11-19 08:31:48
最近一段时间总是被内存对齐这个问题困扰着,上网查找了一些资料,可以说这些资料讲解的都是一个模板,就是为了提高cpu的访问效率,如果对齐的话需要一个时钟周期访问,那么不对齐的话就需要两个时钟周期访问,所以要进行字节对齐,说一句不太好听的话,全是废话,谁不知道是为了提高访问效率,还用他们说,都没有讲解出到底为什么要进行内存对齐(不需要告诉我这是为了提高cpu的效率,呵呵!),cpu是如何通过数据总线来读取内存中的数据?

这个是我个人的理解(不知道对不对):

内存访问粒度是不变的,现在的计算机中cpu的内存访问粒度是四个字节,也就是每次从内存中总是取四个字节的数据,如果不存在char与short类型的数据的话cpu每次都会取得想要的值,不用进行一个剔除就能够得到想要的,可是事实上是存在char类型与short类型的数据,这样cpu在取这两种类型的数据的时候就需要进行剔除了,因此在效率上就存在了一个损失,但是永远都不可能实现“利益”的最大化,虽然在效率上存在一定的损失,可是却节约了一部分的内存空间,字节对齐只是一个折中的方法,这种方法并不是十全十美的!!


计算机都有一个默认的对齐模数,如果说默认对齐的模数是四个字节的话,那么是不是每个类型的数据都会对齐到四个字节呢??比如说一个char类型的数据a和一个short类型的数据1 ,char类型数据起始地址是0x00的话,那么按照默认的四字节对齐,应该是这样的吗?

0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08

0a cc cc cc 01 cc cc cc cc

后面的字节是为了四字节对齐而填充的呢?

不知道我的理解是否是对的!望高手指正!!



但是如果我们设置了对齐字节数的话,比如我们设置了#pragma pack(1),这样计算机中默认的对齐字节数就不再发挥作用了??

这样对齐的字节数就会按照有效对齐字节数来进行字节对齐呢?

如:

0x00 0x01 0x02

0a 01 cc

我的这个理解是对的吗??望指正!!



如果我的理解没有问题的话,那么为什么还要设置字节对齐呢??这样不是在cpu访问效率上是一种损失吗如果是提高cpu的访问效率的话,因为数据总线的宽度是32位的,那为什么不把每一个类型的数据都设置为四个字节对齐呢???难道就是为了节约内存空间而损失了访问效率?

希望高手能够给出一个全面的解释呀!呵呵!

...全文
4822 59 打赏 收藏 转发到动态 举报
写回复
用AI写文章
59 条回复
切换为时间正序
请发表友善的回复…
发表回复
huangtaoyuan 2011-10-20
  • 打赏
  • 举报
回复
不错!
失散糖 2011-10-18
  • 打赏
  • 举报
回复
mark
KAIFEIXIONGDI 2011-10-15
  • 打赏
  • 举报
回复
mark
ouen333 2011-10-14
  • 打赏
  • 举报
回复
[Quote=引用 45 楼 jzy4077 的回复:]
回复楼上的所有回答者:

谢谢你们的回答呀!呵呵!这下让我彻底的明白了呀!!!

然后我对此做一下总结:

cpu每次都是从内存中读取四个字节的数据,这是因为数据总线的宽度是32位的,并且每次cpu都要从地址值是4的倍数的地址开始读取,比如cpu只能够读取起始地址是0x00 , 0x04 以及0x08......位置的数据,这个和编译器字节对齐是两回事,也就是我们通过设置#pragma……
[/Quote]
楼主,首先感谢你开的这个贴,解决了我不少的疑惑。
那么能不能在帮我解决一下这个问题(53楼我说的那个)。谢谢
ouen333 2011-10-14
  • 打赏
  • 举报
回复
[Quote=引用 41 楼 bokutake 的回复:]
你还不明白么?CPU一次只会从4倍数开始的地址一次性读取32位数(数据总线宽度)放到缓存里。然后根据需要剔除不要的部分,拼合、移位后传输到内部寄存器里,保证内部寄存器里的数据总是正确内容。也就是说1字节的数据读取时肯定能读到。2字节的数据可能跨4倍数的界限,这时候就需要两个访存周期读取48位数,然后拼合并剔除不要的部分。这个过程是很浪费时间的。
你改编译器开关只是改变了编译出来的数据排列,CPU……
[/Quote]
对于CPU读取的部分,已经明白了,谢谢你的讲解,
但是对于“你改编译器开关只是改变了编译出来的数据排列” 能不能解释下,谢谢!
swuxd 2011-10-12
  • 打赏
  • 举报
回复
zcm_xh2008 2011-10-12
  • 打赏
  • 举报
回复
mark
sheldon4090 2011-10-12
  • 打赏
  • 举报
回复
非常明白,受教了
laslull 2011-10-12
  • 打赏
  • 举报
回复
V5
mark备用
FrightingGuo 2010-12-22
  • 打赏
  • 举报
回复
[Quote=引用 41 楼 bokutake 的回复:]
你还不明白么?CPU一次只会从4倍数开始的地址一次性读取32位数(数据总线宽度)放到缓存里。然后根据需要剔除不要的部分,拼合、移位后传输到内部寄存器里,保证内部寄存器里的数据总是正确内容。也就是说1字节的数据读取时肯定能读到。2字节的数据可能跨4倍数的界限,这时候就需要两个访存周期读取48位数,然后拼合并剔除不要的部分。这个过程是很浪费时间的。
你改编译器开关只是改变了编译出来的数据排列,CPU……
[/Quote]
呵呵,有点明白了~~
mstlq 2010-11-20
  • 打赏
  • 举报
回复
[Quote=引用 27 楼 jzy4077 的回复:]
引用 10 楼 v2sun 的回复:
我的理解是:

CPU在取数据时使用缓存,缓存每次缓冲固定大小的数据(以4Byte为例),而且是在特定的内存地址上预取数据(在这里是4的倍数);故如果把一个4字节的变量放在非4的倍数的内存地址上,则需要两次预读取才可完成变量访问,便降低了访问速率。
这也是为什么结构体变量定义建议把大变量放在前面的原因吧。


我刚才仔细读了你的这句话,如果我的理……
[/Quote]
原理就是这么个原理……
liups 2010-11-20
  • 打赏
  • 举报
回复
不明白楼主为什么说资料上是废话!?

本人当初微机原理只学过8088和8086,知道如下事实:
1、8088一次访问8位内存,所以不存在对齐的问题;
2、8086一次16位或两个字节,如果这两个字节没有对齐的话,只能访问内存两次
才能获得


所以编译器产生代码时考虑对齐是再正常不过了
RJGC_Bullet 2010-11-20
  • 打赏
  • 举报
回复
简单的说:前端总线的宽度决定了cpu一次能读的数据的长度,在32位的操作系统下,一次是读4个字节的,而且每次读数据时的内存起始地址必须为4的倍数,因此4n~4n+3这样的内存地址内,编译器一般只为其分配最多一个变量.因此你会发现你的结构体中元素的地址并不是严格按照它的长度线性排列的.
iamxmz 2010-11-20
  • 打赏
  • 举报
回复
比如网络通信中要用到以下结构体:
struct test{
char name[2];
long long number1;
long number2;
}
那么必须手动对齐,否则不同环境下的默认对齐不一样.

为了减少此类潜在bug,在实际项目中一般要求所有通信相关的结构体都以统一方式对齐.
点墨 2010-11-20
  • 打赏
  • 举报
回复
[Quote=引用 27 楼 jzy4077 的回复:]

引用 10 楼 v2sun 的回复:
我的理解是:

CPU在取数据时使用缓存,缓存每次缓冲固定大小的数据(以4Byte为例),而且是在特定的内存地址上预取数据(在这里是4的倍数);故如果把一个4字节的变量放在非4的倍数的内存地址上,则需要两次预读取才可完成变量访问,便降低了访问速率。
这也是为什么结构体变量定义建议把大变量放在前面的原因吧。


我刚才仔细读了你的这句话,如果我的……
[/Quote]
这是假设缓存以4字节为单位读取,实际肯定大于它,但固定大小相应地址(相应倍数)的读取应该是没问题的。
总之,我是这么理解的。
辰岡墨竹 2010-11-20
  • 打赏
  • 举报
回复
你还不明白么?CPU一次只会从4倍数开始的地址一次性读取32位数(数据总线宽度)放到缓存里。然后根据需要剔除不要的部分,拼合、移位后传输到内部寄存器里,保证内部寄存器里的数据总是正确内容。也就是说1字节的数据读取时肯定能读到。2字节的数据可能跨4倍数的界限,这时候就需要两个访存周期读取48位数,然后拼合并剔除不要的部分。这个过程是很浪费时间的。
你改编译器开关只是改变了编译出来的数据排列,CPU读取的时候还是4字节4字节的顺序读的。
对于不同的CPU和不同的设置,CPU访存特性是不同的。x86系列CPU都支持不对齐访问,也提供了开关禁用这个机制。有的CPU(ARM的一些型号)根本不支持不对齐访问,如果你的程序中要访问奇数地址,CPU会直接中止执行,抛出异常。
skyworth98 2010-11-20
  • 打赏
  • 举报
回复
[Quote=引用 27 楼 jzy4077 的回复:]
引用 10 楼 v2sun 的回复:
我的理解是:

CPU在取数据时使用缓存,缓存每次缓冲固定大小的数据(以4Byte为例),而且是在特定的内存地址上预取数据(在这里是4的倍数);故如果把一个4字节的变量放在非4的倍数的内存地址上,则需要两次预读取才可完成变量访问,便降低了访问速率。
这也是为什么结构体变量定义建议把大变量放在前面的原因吧。


我刚才仔细读了你的这句话,如果我的理……
[/Quote]

完全正确
aining0809 2010-11-20
  • 打赏
  • 举报
回复
学习ing。。。
JZY4077 2010-11-20
  • 打赏
  • 举报
回复
回复36楼:

那么cpu在访问内存中的数据的时候是按照什么对齐方式来访问的呢??比如数据总线的宽度是四个字节,那我可以理解为每次访问内存的时候都会访问地址是4的倍数吗?比如cpu只能够访问从0x00 ,0x04 ,0x08....开始的地址吗??
lxyppc 2010-11-20
  • 打赏
  • 举报
回复
1. CPU不会因为你的pack设置而改变自己的访问方式,你代码里对齐值是多少,CPU并不不知道

2. CPU在访问对齐数据和没有对齐的数据的时候会使用不同的方法,不同的方法效率。cpu可以访问放在奇数地址的4字节数据,只是效率上与放在4的倍数地址上的4字节数据不同。(效率是指时间和空间上的)

3. 对齐的数据是指数据所在地址能被数据大小整除。如:1个字节的数据在任何位置都是对齐的。2个字节的数据在偶数地址上是对齐的。4个字节的数据在4的倍数地址上是对齐的。

以上是针对cpu而言的,和编译器无关

而编译器的pack开关上面已经说过了。你改变的是你代码中数据的对齐方式,这种方式上的改变会造成有的数据没有对齐。
比如4字节的数据放在了奇数地址。对cpu而言,奇数地址上的4字节数据是没有对齐的,cpu在访问的时候效率上有不同。

pack开关影响的是数据在内存中的排列方式。cpu可以访问任何对齐形式的数据,只是效率不同。
加载更多回复(32)

69,371

社区成员

发帖
与我相关
我的任务
社区描述
C语言相关问题讨论
社区管理员
  • C语言
  • 花神庙码农
  • 架构师李肯
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧