首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 开发语言 > C语言 >

到底为什么要进行字节对齐?该怎么处理

2012-04-01 
到底为什么要进行字节对齐?最近一段时间总是被内存对齐这个问题困扰着,上网查找了一些资料,可以说这些资料

到底为什么要进行字节对齐?
最近一段时间总是被内存对齐这个问题困扰着,上网查找了一些资料,可以说这些资料讲解的都是一个模板,就是为了提高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位的,那为什么不把每一个类型的数据都设置为四个字节对齐呢???难道就是为了节约内存空间而损失了访问效率?

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



[解决办法]
CPU 的访问粒度不仅仅是大小限制,地址上也有限制。也就是说,CPU 只能访问对齐地址上的固定长度的数据。
以四字节对齐为例,就是只能访问 0x0 - 0x3,0x4 - 0x7, 0x8 - 0xc 这样的(闭)区间,不能跨区间访问。
如果真正需要访问的数据并没有占据那个区间的全部字节范围,还有另外的信号线来指出具体操作哪几个字节,类似于掩码的作用。好像也有些架构干脆就不允许这种部分访问,强制要求按粒度访问。

如果一个数据跨越了两个这样的区间,那么就只能将这个数据的操作拆分成两部分,分别执行,效率当然就低了。

解决这个问题的一个办法就是强制数据对齐,现在假设一个 16 字节对齐的系统(稍微新一点的 x86 架构应该都是 16 字节对齐):
显然,一个字节无论如何不会跨越两个对齐区间;
当一个 2 字节的数据放在奇数地址上,就有可能跨越两个区间,但放在偶数地址上(即 2 字节对齐)就肯定不会;
同样,只要 4 字节数据放在 4 字节地址上,8 字节数据放在 8 字节地址上,一定不会跨越两个区间。

这就是比较常见的字节对齐策略,将长度小于一个对齐单位的数据对齐到不小于它的长度的首个二的幂次。(晕,这句话好绕……)
[解决办法]
假定你工作在32位机上,
假定你分配的变量的起始地址为0x00000001H,是一个占四字节空间的整形量。
这个地址就不是按照四字节地址对齐的,处理器在访问这个变量是,由于处理器的数据总线位宽是32位,所以处理器必须进行两次内存访问操作,第一次从地址0x00000000h-0x00000003读入一次,第二次从0x00000004-0x00000007读入一次,然后再将0x00000001H-0x00000003和0x00000004H的内容进行组合,才得到了你想要获得的数据,这样会造成性能的下降。

当你使用编译器开关进行4字节对齐时,只需要一次访内操作,就可以读入一个整型数据。
[解决办法]
去看下计算机组成原理你就知道的太多了
[解决办法]
为啥要对齐,这个要从总线的硬件实现上来讲了
而你在编代码的时候写的#pragma pack(1),并不会改变CPU访问总线的方式
#pragma pack(1)只是让编译器不再对齐,而没有让CPU访问内存不对齐
这硬件对齐而软件不对齐的差异要靠CPU花时间来解决。
[解决办法]

[解决办法]
一次访问时,要么读0x01~0x04,要么读0x05~0x08……
硬件不支持一次访问就读到0x02~0x05
举个例子,如果0x02~0x05存了一个int,读取这个int就需要先读0x01~0x04,留下0x02~0x04的内容,再读0x05~0x08,留下0x05的内容,两部分拼接起来才能得到那个int的值……
读一个int就要两次内存访问,效率就低了……



[解决办法]
我的理解是:

CPU在取数据时使用缓存,缓存每次缓冲固定大小的数据(以4Byte为例),而且是在特定的内存地址上预取数据(在这里是4的倍数);故如果把一个4字节的变量放在非4的倍数的内存地址上,则需要两次预读取才可完成变量访问,便降低了访问速率。
这也是为什么结构体变量定义建议把大变量放在前面的原因吧。
[解决办法]
#pragma pack(n)
只是告诉编译器所需要使用的对齐方式,然后编译器会按要求生成目标代码,供CPU执行
而与CPU存取操作无关。
建议看下微机原理-》CPU如何读写寄存器
[解决办法]
计算机内存分配与管理,计算机组成原理,了解CPU的总线周期,这问题你就明白了
[解决办法]

探讨

回复5楼:
如果#pragma pack(n)并不会改变cpu访问内存的对齐方式,那么为什么还要进行这样一个让cpu降低效率的工作呢?难道只是为了节约一点内存空间吗?



[解决办法]
学习学习。。。
[解决办法]
探讨

回复9楼:
你说的会:要么读0x01~0x04,要么读0x05~0x08,这样就不是对齐了吗?(难道是按照一个字节对齐的吗?)

举个例子,如果0x02~0x05存了一个int,读取这个int就需要先读0x01~0x04,留下0x02~0x04的内容,再读0x05~0x08,留下0x05的内容,两部分拼接起来才能得到那个int的值……

按照你的这种说法读取数据的时候岂不是不对齐了??……

[解决办法]
我不懂,只能听各位讲解了
[解决办法]
探讨

回复11楼:
如果这个#pragma pack(n)和cpu的读取无关,为什么还要进行这样一个工作呢?

是为了节约内存空间吗?

[解决办法]
效率什么的我不精通,就不献丑了。
一般特意指明#pragma pack(n)的多半是为了让双方对数据结构解析做到一致,不会因为机器或编译器的缘故导致同样的数据结构大小不一样,这样的话就乱套了。虽然现在的主流是默认4字节对齐,但是你不能保证嵌入式系统或其他系统一定会使用4字节对齐。
[解决办法]
C/C++ code
//先是自动内存对齐者#include <stdio.h>#pragma pack(1)struct tagMyStruct{//12 Byte  char ch ;        //1 Byte  short sx ;       //2 Byte  char bh ;        //1 Byte  int x ;          //4 Byte};int main(){        struct tagMyStruct try = {-1, 0, 0, -1};        printf("struct:%d\nshort:%d\nint:%d\n",sizeof(struct tagMyStruct),sizeof(short),sizeof(int));        return 0;}
[解决办法]
上面三个是内存对齐的,先是代码,再是运行结果,再是GDB调试内存结果;
下面是#pragma pack(1)后的,结构同上。

你可以自己试一下,不同编译器不知是否相同。
[解决办法]

[解决办法]
探讨

引用:
我的理解是:

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


我刚才仔细读了你的这句话,如果我的……

[解决办法]
比如网络通信中要用到以下结构体:
struct test{
char name[2];
long long number1;
long number2;
}
那么必须手动对齐,否则不同环境下的默认对齐不一样.

为了减少此类潜在bug,在实际项目中一般要求所有通信相关的结构体都以统一方式对齐.
[解决办法]
简单的说:前端总线的宽度决定了cpu一次能读的数据的长度,在32位的操作系统下,一次是读4个字节的,而且每次读数据时的内存起始地址必须为4的倍数,因此4n~4n+3这样的内存地址内,编译器一般只为其分配最多一个变量.因此你会发现你的结构体中元素的地址并不是严格按照它的长度线性排列的.
[解决办法]
不明白楼主为什么说资料上是废话!?

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


所以编译器产生代码时考虑对齐是再正常不过了
[解决办法]
探讨
引用:
我的理解是:

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


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

[解决办法]
9楼理解正确,别传牛角尖
[解决办法]
1. CPU不会因为你的pack设置而改变自己的访问方式,你代码里对齐值是多少,CPU并不不知道

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

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

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



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

pack开关影响的是数据在内存中的排列方式。cpu可以访问任何对齐形式的数据,只是效率不同。
[解决办法]

探讨
引用:
我的理解是:

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


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

[解决办法]
Assembly code
[u]Optimizing Memor y Access[/u]Memory access is one of the slowest functions the processor performs. When writing assembly languageprograms that require high performance, it is best to avoid memory access as much as possible.Whenever possible, it is best to keep variables in registers on the processor. Register access is highlyoptimized for the processor, and is the quickest way to handle data.When it is not possible to keep all of the application data in registers, you should try to optimize thememory access for the application. For processors that use data caching, accessing memory in a sequen-tial order in memory helps increase cache hits, as blocks of memory will be read into cache at one time.One other item to think about when using memory is how the processor handles memory reads andwrites. Most processors (including those in the IA-32 family) are optimized to read and write memorylocations in specific cache blocks, beginning at the start of the data section. On a Pentium 4 processor, thesize of the cache block is 64 bits. If you define a data element that crosses a 64-bit block boundary, it willrequire two cache operations to retrieve or store the data element in memory.To solve this problem, Intel suggests following these rules when defining data:? Align 16-bit data on a 16-byte boundary.? Align 32-bit data so that its base address is a multiple of four.? Align 64-bit data so that its base address is a multiple of eight.? Avoid many small data transfers. Instead, use a single large data transfer.? Avoid using larger data sizes (such as 80- and 128-bit floating-point values) in the stack.Aligning data within the data section can be tricky. The order in which data elements are defined can becrucial to the performance of your application. If you have a lot of similarly sized data elements, such asinteger and floating-point values, place them together at the beginning of the data section. This ensuresthat they will maintain the proper alignment. If you have a lot of odd-sized data elements, such asstrings and buffers, place those at the end of the data section so they won’t throw off the alignment ofthe other data elements. 

热点排行