现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特定的内存地址访问,这就需要各种类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。
对齐的作用和原因:各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。比如有些架构的CPU在访问 一个没有进行对齐的变量的时候会发生错误,那么在这种架构下编程必须保证字节对齐.其他平台可能没有这种情况,但是最常见的是如果不按照适合其平台要求对 数据存放进行对齐,会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始,如果一个int型(假设为32位系统)如果存放在偶地址开始的地方,那 么一个读周期就可以读出这32bit,而如果存放在奇地址开始的地方,就需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该32bit数 据。显然在读取效率上下降很多。
先让我们看几个例子吧(32bit,x86环境,gcc编译器):
设结构体如下定义:
struct A
{
int a;
char b;
short c;
};
struct B
{
char b;
int a;
short c;
};
现在已知32位机器上各种数据类型的长度如下:
char:1(有符号无符号同)
short:2(有符号无符号同)
int:4(有符号无符号同)
long:4(有符号无符号同)
float:4 double:8
那么上面两个结构大小如何呢?
结果是:
sizeof(strcut A)值为8
sizeof(struct B)的值却是12
结构体A中包含了4字节长度的int一个,1字节长度的char一个和2字节长度的short型数据一个,B也一样;按理说A,B大小应该都是7字节。
之所以出现上面的结果是因为编译器要对数据成员在空间上进行对齐。上面是按照编译器的默认设置进行对齐的结果,那么我们是不是可以改变编译器的这种默认对齐设置呢,当然可以.例如:
#pragma pack (2) /*指定按2字节对齐*/
struct C
{
char b;
int a;
short c;
};
#pragma pack () /*取消指定对齐,恢复缺省对齐*/
sizeof(struct C)值是8。
修改对齐值为1:
#pragma pack (1) /*指定按1字节对齐*/
struct D
{
char b;
int a;
short c;
};
#pragma pack () /*取消指定对齐,恢复缺省对齐*/
sizeof(struct D)值为7。
后面我们再讲解#pragma pack()的作用.
先让我们看四个重要的基本概念:
1.数据类型自身的对齐值:
对于char型数据,其自身对齐值为1,对于short型为2,对于int,float,double类型,其自身对齐值为4,单位字节。(double 应该是8)
2.结构体或者类的自身对齐值:其成员中自身对齐值最大的那个值。
3.指定对齐值:#pragma pack (value)时的指定对齐值value。
4.数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中小的那个值。
有了这些值,我们就可以很方便的来讨论具体数据结构的成员和其自身的对齐方式。有效对齐值N是最终用来决定数据存放地址方式的值,最重要。有效对齐N,就是 表示“对齐在N上”,也就是说该数据的存放的起始地址满足:"存放起始地址%N=0".而数据结构中的数据变量都是按定义的先后顺序来排放的。第一个数据变量的起始地址就是数据结构的起始地址。结构体的成员变量要对齐排放,结构体本身也要根据自身的有效对齐值圆整(就是结构体成员变量占用总长度需要是对结构体有效对齐值的整数倍,结合下面例子理解)。这样就不难理解上面的几个例子的值了。
例子分析:
分析例子B;
struct B
{
char b;
int a;
short c;
};
假 设B从地址空间0x0000开始排放。该例子中没有定义指定对齐值,在笔者环境下,该值默认为4。第一个成员变量b的自身对齐值是1,比指定或者默认指定 对齐值4小,所以其有效对齐值为1,所以其存放地址0x0000符合0x0000%1=0.第二个成员变量a,其自身对齐值为4,所以有效对齐值也为4, 所以只能存放在起始地址为0x0004到0x0007这四个连续的字节空间中,复核0x0004%4=0,且紧靠第一个变量。第三个变量c,自身对齐值为 2,所以有效对齐值也是2,可以存放在0x0008到0x0009这两个字节空间中,符合0x0008%2=0。所以从0x0000到0x0009存放的 都是B内容。再看数据结构B的自身对齐值为其变量中最大对齐值(这里是a)所以就是4,所以结构体的有效对齐值也是4。根据结构体圆整的要求, 0x0009到0x0000=10字节,(10+2)%4=0。所以0x0000A到0x000B也为结构体B所占用。故B从0x0000到0x000B 共有12个字节,sizeof(struct B)=12;其实如果就这一个就来说它已将满足字节对齐了, 因为它的起始地址是0,因此肯定是对齐的,之所以在后面补充2个字节,是因为编译器为了实现结构数组的存取效率,试想如果我们定义了一个结构B的数组,那 么第一个结构起始地址是0没有问题,但是第二个结构呢?按照数组的定义,数组中所有元素都是紧挨着的,如果我们不把结构的大小补充为4的整数倍,那么下一 个结构的起始地址将是0x0000A,这显然不能满足结构的地址对齐了,因此我们要把结构补充成有效对齐大小的整数倍.其实诸如:对于char型数据,其 自身对齐值为1,对于short型为2,对于int,float,double类型,其自身对齐值为4,这些已有类型的自身对齐值也是基于数组考虑的,只 是因为这些类型的长度已知了,所以他们的自身对齐值也就已知了.
同理,分析上面例子C:
#pragma pack (2) /*指定按2字节对齐*/
struct C
{
char b;
int a;
short c;
};
#pragma pack () /*取消指定对齐,恢复缺省对齐*/
第 一个变量b的自身对齐值为1,指定对齐值为2,所以,其有效对齐值为1,假设C从0x0000开始,那么b存放在0x0000,符合0x0000%1= 0;第二个变量,自身对齐值为4,指定对齐值为2,所以有效对齐值为2,所以顺序存放在0x0002、0x0003、0x0004、0x0005四个连续 字节中,符合0x0002%2=0。第三个变量c的自身对齐值为2,所以有效对齐值为2,顺序存放
在0x0006、0x0007中,符合 0x0006%2=0。所以从0x0000到0x00007共八字节存放的是C的变量。又C的自身对齐值为4,所以C的有效对齐值为2。又8%2=0,C 只占用0x0000到0x0007的八个字节。所以sizeof(struct C)=8.
1.在VC IDE中,可以这样修改:[Project]|[Settings],c/c++选项卡Category的Code Generation选项的Struct Member Alignment中修改,默认是8字节。
2.在编码时,可以这样动态修改:#pragma pack .注意:是pragma而不是progma.
如果在编程的时候要考虑节约空间的话,那么我们只需要假定结构的首地址是0,然后各个变量按照上面的原则进行排列即可,基本的原则就是把结构中的变量按照 类型大小从小到大声明,尽量减少中间的填补空间.还有一种就是为了以空间换取时间的效率,我们显示的进行填补空间进行对齐,比如:有一种使用空间换时间做 法是显式的插入reserved成员:
struct A{
char a;
char reserved[3];//使用空间换时间
int b;
}
reserved成员对我们的程序没有什么意义,它只是起到填补空间以达到字节对齐的目的,当然即使不加这个成员通常编译器也会给我们自动填补对齐,我们自己加上它只是起到显式的提醒作用.
代码中关于对齐的隐患,很多是隐式的。比如在强制类型转换的时候。例如:
unsigned int i = 0x12345678;
unsigned char *p=NULL;
unsigned short *p1=NULL;
p=&i;
*p=0x00;
p1=(unsigned short *)(p+1);
*p1=0x0000;
最后两句代码,从奇数边界去访问unsignedshort型变量,显然不符合对齐的规定。
在x86上,类似的操作只会影响效率,但是在MIPS或者sparc上,可能就是一个error,因为它们要求必须字节对齐.
如果出现对齐或者赋值问题首先查看
1. 编译器的big little端设置
2. 看这种体系本身是否支持非对齐访问
3. 如果支持看设置了对齐与否,如果没有则看访问时需要加某些特殊的修饰来标志其特殊访问操作。
八.相关文章:转自http://blog.csdn.net/goodluckyxl/archive/2005/10/17/506827.aspx
ARM下的对齐处理
from DUI0067D_ADS1_2_CompLib
3.13 type qulifiers
有部分摘自ARM编译器文档对齐部分
对齐的使用:
1.__align(num)
这个用于修改最高级别对象的字节边界。在汇编中使用LDRD或者STRD时
就要用到此命令__align(8)进行修饰限制。来保证数据对象是相应对齐。
这个修饰对象的命令最大是8个字节限制,可以让2字节的对象进行4字节
对齐,但是不能让4字节的对象2字节对齐。
__align是存储类修改,他只修饰最高级类型对象不能用于结构或者函数对象。
2.__packed
__packed是进行一字节对齐
1.不能对packed的对象进行对齐
2.所有对象的读写访问都进行非对齐访问
3.float及包含float的结构联合及未用__packed的对象将不能字节对齐
4.__packed对局部整形变量无影响
5.强制由unpacked对象向packed对象转化是未定义,整形指针可以合法定
义为packed。
__packed int* p; //__packed int 则没有意义
6.对齐或非对齐读写访问带来问题
__packed struct STRUCT_TEST
{
char a;
int b;
char c;
} ; //定义如下结构此时b的起始地址一定是不对齐的
//在栈中访问b可能有问题,因为栈上数据肯定是对齐访问[from CL]
//将下面变量定义成全局静态不在栈上
static char* p;
static struct STRUCT_TEST a;
void Main()
{
__packed int* q; //此时定义成__packed来修饰当前q指向为非对齐的数据地址下面的访问则可以
p = (char*)&a;
q = (int*)(p+1);
*q = 0x87654321;
/*
得到赋值的汇编指令很清楚
ldr r5,0x20001590 ; = #0x12345678
[0xe1a00005] mov r0,r5
[0xeb0000b0] bl __rt_uwrite4 //在此处调用一个写4byte的操作函数
[0xe5c10000] strb r0,[r1,#0] //函数进行4次strb操作然后返回保证了数据正确的访问
[0xe1a02420] mov r2,r0,lsr #8
[0xe5c12001] strb r2,[r1,#1]
[0xe1a02820] mov r2,r0,lsr #16
[0xe5c12002] strb r2,[r1,#2]
[0xe1a02c20] mov r2,r0,lsr #24
[0xe5c12003] strb r2,[r1,#3]
[0xe1a0f00e] mov pc,r14
*/
/*
如果q没有加__packed修饰则汇编出来指令是这样直接会导致奇地址处访问失败
[0xe59f2018] ldr r2,0x20001594 ; = #0x87654321
[0xe5812000] str r2,[r1,#0]
*/
//这样可以很清楚的看到非对齐访问是如何产生错误的
//以及如何消除非对齐访问带来问题
//也可以看到非对齐访问和对齐访问的指令差异导致效率问题
}
PS:读后
1、明确结构的指定对齐值,如VC2005下是8。或者是由语句“#pragma pack (value)”指定的值。
2、按照结构数据变量顺序相应计算。按照结构的指定对齐值进行占位计算。举例说明(默认对齐为8位):
以下结构中,
结构体或者类的自身对齐值(其成员中自身对齐值最大的那个值)为:
double 长度为8
指定对齐值为默认对齐值为8位。
typedef struct
{
char x1; //占据第1个8位的前1位,第1个8位剩下7位
double x2; //剩下的7位无法满足长度,本长度为8,开辟一个新的8位,第2个8位刚好用完
size_t x3; //开辟第3个8位,占据其中4位,剩余4位。
unsigned short x4; //将第3个8位的后4位占据,第3个8位刚好用完
int x5; //开辟第4个8位,占据前4位
double x6; //开辟第5个8位,刚好用完
char x7; //开辟第6个8位,占据一位。其余为对齐占位,总计6*8=48位
}x;
以下结构中,
结构体或者类的自身对齐值(其成员中自身对齐值最大的那个值)为:
double 长度为8,因为指定为4,所以还是为4
指定对齐值4位。
#pragma pack (4)
typedef struct
{
char y1; //占据第1个4位的前1位,第一个4位剩下3位
double y2; //剩下的3位无法满足长度,本长度为8,开辟2个新的4位,刚好用完
size_t y3; //开辟第4个4位,占据其中4位,刚好用完。
unsigned short y4; //开辟第5个4位,占用2位
int y5; //开辟第6个4位,刚好用完
double y6; //开辟第7、8个4位,刚好用完
char y7; //开辟第9个4位,占据1位。其余3位补齐。总计4*9=36位
}y;
#pragma pack ()
转载地址:http://blog.chinaunix.net/uid-14802518-id-2784907.html
三个例子
共用体两个不能:两个不能:不能使用共用体变量,只能引用共用体变量中的成员。不能在定义共用体变量时进行初始化。
0:数组的对齐值看起原有的类型,大小只是贡献多少,而在一个结构体中包含另外一个结构体,该结构体可能贡献自身对齐值和占有空间大小是两个概念;
test2 自身对其值为int类型4,所以sizeof(test2)= 20 (最后一个char要圆整);
// 如果包含在另外一个struct中,那么它占有20个字节,但是它的有效对齐值还是其自身对齐值4,只要存放的地址是4的倍数就行
struct test2{
int aa[2];
char cc[9];
};
// test4 自身对齐值为其数据成员最大对齐值即8,故sizeof(test4)=16,,
//如果包含在另外一个struct中,那么它占有16个字节,但是它的有效对齐值还是其自身对齐值8,只要存放的地址是4的倍数就行
struct test4{
int a;
double d;
};
// test3的自身对齐值由test4提供为8个字节,而test4占用16个字节,ab占用4个,后圆整所以sizeof(test3)=24
struct test3{
test4 t4; // 占有16个字节存储空间,但是它的自身对齐值为8即double类型, 所以test3的最大对齐值也为8
int ab;
};
// test5的自身对齐值为4, 而test2占用20个字节,ab占用4个字节,故sizeof(test5) = 24
struct test5{
test2 tt;
int ab;
};
1:
// 共用体类型,所有的数据成员共享一个内存空间,自身对齐值为成员中最大的数据成员的对齐值
// 自身对齐值为数据成员的最大对齐值为4个字节, 而char a[5] 需要5个字节,故union占用8个字节sizeof(uxx) = 8
union uxx{
int ix;
char a[5];
}x;
// test自身对齐值由x提供,为4个字节即为其有效对齐值, sizeof(test) = 12
struct test{
bool a;
union uxx x; // 有效对齐值为4,但是占有8个字节,
};
int main(){
cout << sizeof(test) << endl; // 12
cout << sizeof(uxx) << endl; // 8
return 0;
}
2:
struct test{
bool a;
union uxx{
int ix;
char a[5];
}x;
};
int main(){
cout << sizeof(test) << endl; // 12
cout << sizeof(test::uxx) << endl; // 8
return 0;
}
3:
struct test{
bool a;
union uxx{ // 这里只是定义共用体类型 而没有定义对象;结构体共用体,只有定义对象才会开辟内存空间的
int ix;
char a[5];
};
};
int main(){
cout << sizeof(test) << endl; // 1 只有一个bool类型,占有内存空间
cout << sizeof(test::uxx) << endl; // 8
return 0;
}
4:
// // a,b,c,d,e的值为0,1,2,3,4, 最大类型成员的对齐值为4个字节,而枚举变量enum etest x;只能取常量中的一个,
// 所以: sizeof(etest)=4 这个与结构体共用体不一样。
enum etest{a,b,c,d,e,f,g,h}; // a,b,c,d,e的值为0,1,2,3,4, 最大类型成员的对齐值为4个字节,所以sizeof(etest) = 4
struct test{
bool a;
union uxx{ // 这里只是定义共用体类型 而没有定义对象;结构体共用体,只有定义对象才会开辟内存空间的
int ix;
char a[5];
};
enum etest xx;
};
int main(){
cout << sizeof(test) << endl; // 8 最大成员为枚举类型etest xx,占用4个字节,bool类型,为了对齐,也要占有4个字节内存空间
cout << sizeof(test::uxx) << endl; // 8
cout << sizeof(etest) << endl; //4
enum etest et; // 枚举类型变量,只能给枚举类型变量进行赋值枚举常量,不能给枚举常量(a,b,c,d,e)复制
//cout << et << endl; // 出错 使用初始化变量
et = c;
cout << et << endl; // 2
et = (enum etest)10; // 必须进行强制转换
cout << et << endl; // 10
cout << sizeof(unsigned int) << endl; // 4
return 0;
}
5:
class A
{
int i;
union U
{
char buff[13];
int i;
}u;
void foo() { }
typedef char* (*f)(void*);
enum{red, green, blue} color;
}a;
注意sizeof计算的是栈中分配的内存,因此当struct中存在static数据成员时,则不考虑在内。。。
struct A{
A(){};
~A(){};
int m1;
int m2;
};
struct B:A{
B(){}
~B(){}
int m1;
char m2;
static char m3;
};
struct C{
C(){}
virtual~C(){}
int m1;
short m2;
};
int main(){
cout << sizeof(A) << endl; // 8
cout << sizeof(B) << endl; // 16
cout << sizeof(C) << endl; //12
return 0;
}
大小端模式
而如int a[4]; 那么&a+1这里+1实际是加了sizeof(int)*4个字节,因为这里的&a是指向数组的指针,是个数组指针,就是int(*b)[4],,这里的b就是&a,,, 而数组这个对象含有sizeof(int)*4个字节。。而int *b[4]; 这是一个指针数组,数组中每个元素都是int*即指针。。
int main(int argc, char **argv)
{
int a[4] = {1, 2, 3, 4};
int *ptr = (int *)(&a + 1);
printf("%d", *(ptr - 1));
}
这段代码返回的就是4
来自:http://www.nowcoder.com/test/question/done?tid=593855&qid=15949#summary
【大端(Big Endian)与小端(Little Endian)简介】
Byte Endian是指字节在内存中的组织,所以也称它为Byte Ordering,或Byte Order。
对于数据中跨越多个字节的对象, 我们必须为它建立这样的约定:
(1) 它的地址是多少?
(2) 它的字节在内存中是如何组织的?
针对第一个问题,有这样的解释:
对于跨越多个字节的对象,一般它所占的字节都是连续的,它的地址等于它所占字节最低地址。(链表可能是个例外, 但链表的地址可看作链表头的地址)。
比如: int x, 它的地址为0×100。 那么它占据了内存中的Ox100, 0×101, 0×102, 0×103这四个字节(32位系统,所以int占用4个字节)。
上面只是内存字节组织的一种情况: 多字节对象在内存中的组织有一般有两种约定。 考虑一个W位的整数。
它的各位表达如下:[Xw-1, Xw-2, ... , X1, X0],它的
MSB (Most Significant Byte, 最高有效字节)为 [Xw-1, Xw-2, ... Xw-8];
LSB (Least Significant Byte, 最低有效字节)为 [X7,X6,..., X0]。
其余的字节位于MSB, LSB之间。
LSB和MSB谁位于内存的最低地址, 即谁代表该对象的地址?
这就引出了大端(Big Endian)与小端(Little Endian)的问题。
如果LSB在MSB前面, 既LSB是低地址, 则该机器是小端; 反之则是大端。
DEC (Digital Equipment Corporation,现在是Compaq公司的一部分)和Intel的机器(X86平台)一般采用小端。
IBM, Motorola(Power PC), Sun的机器一般采用大端。
当然,这不代表所有情况。有的CPU即能工作于小端, 又能工作于大端, 比如ARM, Alpha,摩托罗拉的PowerPC。 具体情形参考处理器手册。
具体这类CPU是大端还是小端,应该和具体设置有关。
(如,Power PC支持little-endian字节序,但在默认配置时是big-endian字节序)
一般来说,大部分用户的操作系统(如windows, FreeBsd,Linux)是Little Endian的。少部分,如MAC OS ,是Big Endian 的。
所以说,Little Endian还是Big Endian与操作系统和芯片类型都有关系。
Linux系统中,你可以在/usr/include/中(包括子目录)查找字符串BYTE_ORDER(或
_BYTE_ORDER, __BYTE_ORDER),确定其值。BYTE_ORDER中文称为字节序。这个值一般在endian.h或machine/endian.h文件中可以找到,有时在feature.h中,不同的操作系统可能有所不同。
对于一个数0×1122
使用Little Endian方式时,低字节存储0×22,高字节存储0×11
而使用Big Endian方式时, 低字节存储0×11, 高字节存储0×22
经一网友指正,才知道,上面的描述,是不准确的.
想了下,觉得如下描述可能更合适:
使用Little Endian方式存储数据时,数据的LSB相对最没意义的数据位,存放在低地址位置,这里的LSB也就是22了.也即,
低地址存储0×22, 高地址存储0×11
而使用Big Endian方式存储数据时,数据的MSB最有意义的数据位,存放在低地址位置,这里的MSB也就是11了.也即
低地址存储0×11, 高地址存储0×22
助记:
1)所谓MSB (Most Significant Byte),名字很复杂,不知是否有人没搞懂,反正我开始看到这个词时候,就很糊涂,有点不完全理解.其实简单说MSB就是,一个数字中,最重要的那位,
举例来说,12004,中文读作,一万两千零四,那最高位的1,就表示了一万,此处就称作MSB,最有意义的位.
2)一般常见的数据存储,用文字写出来的时候,其内容书写格式,多数是从低地址到高地址.(更符合人类思维的原因)
举例,一个16进制数是 0×11 22 33, 而存放的位置是
地址0×3000 中存放11
地址0×3001 中存放22
地址0×3002 中存放33
连起来就写成地址0×3000-0×3002中存放了数据0×112233.
而这种存放和表示方式,正好符合大端.
解释的有点乱,希望有人能看懂.
如果还有哪里有误,还请各位继续指正.谢谢.
【用函数判断系统是Big Endian还是Little Endian】
bool IsBig_Endian()
//如果字节序为big-endian,返回true;
//反之为 little-endian,返回false
{
unsigned short test = 0×1122;
if(*( (unsigned char*) &test ) == 0×11)
return TRUE;
else
return FALSE;
}//IsBig_Endian()
转自http://www.cnblogs.com/okaimee/archive/2010/07/19/1780609.html
====================================================================
====================================================================
http://wxxweb.blog.163.com/blog/static/135126900201022133740759/
大端模式与小端模式一、概念及详解
在各种体系的计算机中通常采用的字节存储机制主要有两种: big-endian和little-endian,即大端模式和小端模式。
先回顾两个关键词,MSB和LSB:
MSB:Most Significant Bit ——- 最高有效位
LSB:Least Significant Bit ——- 最低有效位
大端模式(big-edian) big-endian:MSB存放在最低端的地址上。
举例,双字节数0×1234以big-endian的方式存在起始地址0×00002000中:
| data |<– address
| 0×12 |<– 0×00002000
| 0×34 |<– 0×00002001
在Big-Endian中,对于bit序列中的序号编排方式如下(以双字节数0×8B8A为例):
bit | 0 1 2 3 4 5 6 7 | 8 9 10 11 12 13 14 15
——MSB———————————-LSB
val | 1 0 0 0 1 0 1 1 | 1 0 0 0 1 0 1 0 |
+——————————————–+
= 0×8 B 8 A
小端模式(little-endian) little-endian:LSB存放在最低端的地址上。
举例,双字节数0×1234以little-endian的方式存在起始地址0×00002000中:
| data |<– address
| 0×34 |<– 0×00002000
| 0×12 |<– 0×00002001
在Little-Endian中,对于bit序列中的序号编排和Big-Endian刚好相反,其方式如下(以双字节数0×8B8A为例):
bit | 15 14 13 12 11 10 9 8 | 7 6 5 4 3 2 1 0
——MSB———————————–LSB
val | 1 0 0 0 1 0 1 1 | 1 0 0 0 1 0 1 0 |
+———————————————+
= 0×8 B 8 A
二、数组在大端小端情况下的存储:
以unsigned int value = 0×12345678为例,分别看看在两种字节序下其存储情况,我们可以用unsigned char buf[4]来表示value: Big-Endian: 低地址存放高位,如下:
高地址
—————
buf[3] (0×78) — 低位
buf[2] (0×56)
buf[1] (0×34)
buf[0] (0×12) — 高位
—————
低地址
Little-Endian: 低地址存放低位,如下:
高地址
—————
buf[3] (0×12) — 高位
buf[2] (0×34)
buf[1] (0×56)
buf[0] (0×78) — 低位
————–
低地址