struct 字节对齐详解与大小端模式

一.什么是字节对齐,为什么要对齐?

    现代计算机中内存空间都是按照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:数组的对齐值看起原有的类型,大小只是贡献多少,而在一个结构体中包含另外一个结构体,该结构体可能贡献自身对齐值和占有空间大小是两个概念;

  1.  test2 自身对其值为int类型4,所以sizeof(test2)= 20 (最后一个char要圆整);   
  2. //   如果包含在另外一个struct中,那么它占有20个字节,但是它的有效对齐值还是其自身对齐值4,只要存放的地址是4的倍数就行  
  3. struct test2{      
  4.     int aa[2];  
  5.     char cc[9];  
  6. };  
  7.   
  8. // test4 自身对齐值为其数据成员最大对齐值即8,故sizeof(test4)=16,,  
  9. //如果包含在另外一个struct中,那么它占有16个字节,但是它的有效对齐值还是其自身对齐值8,只要存放的地址是4的倍数就行  
  10. struct test4{     
  11.     int a;  
  12.     double d;  
  13. };  
  14.   
  15. // test3的自身对齐值由test4提供为8个字节,而test4占用16个字节,ab占用4个,后圆整所以sizeof(test3)=24  
  16. struct test3{  
  17.     test4 t4;     // 占有16个字节存储空间,但是它的自身对齐值为8即double类型, 所以test3的最大对齐值也为8  
  18.     int ab;  
  19. };  
  20.   
  21. //  test5的自身对齐值为4, 而test2占用20个字节,ab占用4个字节,故sizeof(test5) = 24  
  22. struct test5{  
  23.     test2 tt;  
  24.     int ab;  
  25. };  
 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:

  1. // 共用体类型,所有的数据成员共享一个内存空间,自身对齐值为成员中最大的数据成员的对齐值  
  2. // 自身对齐值为数据成员的最大对齐值为4个字节, 而char a[5] 需要5个字节,故union占用8个字节sizeof(uxx) = 8  
  3. union uxx{  
  4.     int ix;  
  5.     char a[5];  
  6. }x;  
  7. //  test自身对齐值由x提供,为4个字节即为其有效对齐值, sizeof(test) = 12  
  8. struct test{  
  9.     bool a;  
  10.     union uxx x;    // 有效对齐值为4,但是占有8个字节,  
  11. };  
  12.   
  13.   
  14. int main(){  
  15.     cout << sizeof(test) << endl;      // 12  
  16.     cout << sizeof(uxx) << endl;        // 8  
  17.     return 0;  
  18. }  
// 共用体类型,所有的数据成员共享一个内存空间,自身对齐值为成员中最大的数据成员的对齐值
// 自身对齐值为数据成员的最大对齐值为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:

  1. struct test{  
  2.     bool a;  
  3.     union uxx{  
  4.         int ix;  
  5.         char a[5];  
  6.     }x;  
  7. };  
  8.   
  9.   
  10. int main(){  
  11.     cout << sizeof(test) << endl;      // 12  
  12.     cout << sizeof(test::uxx) << endl;        // 8  
  13.     return 0;  
  14. }  
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:

  1. struct test{  
  2.     bool a;  
  3.     union uxx{          // 这里只是定义共用体类型 而没有定义对象;结构体共用体,只有定义对象才会开辟内存空间的  
  4.         int ix;  
  5.         char a[5];  
  6.     };  
  7. };  
  8.   
  9.   
  10. int main(){  
  11.     cout << sizeof(test) << endl;      // 1  只有一个bool类型,占有内存空间  
  12.     cout << sizeof(test::uxx) << endl;        // 8  
  13.     return 0;  
  14. }  
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:

  1. // // a,b,c,d,e的值为0,1,2,3,4,   最大类型成员的对齐值为4个字节,而枚举变量enum etest x;只能取常量中的一个,  
  2. //       所以: sizeof(etest)=4  这个与结构体共用体不一样。  
  3. enum etest{a,b,c,d,e,f,g,h};     // a,b,c,d,e的值为0,1,2,3,4,   最大类型成员的对齐值为4个字节,所以sizeof(etest) = 4  
  4.   
  5.   
  6. struct test{  
  7.     bool a;  
  8.     union uxx{          // 这里只是定义共用体类型 而没有定义对象;结构体共用体,只有定义对象才会开辟内存空间的  
  9.         int ix;  
  10.         char a[5];  
  11.     };  
  12.     enum etest xx;  
  13. };  
  14.   
  15.   
  16. int main(){  
  17.     cout << sizeof(test) << endl;      // 8  最大成员为枚举类型etest xx,占用4个字节,bool类型,为了对齐,也要占有4个字节内存空间   
  18.     cout << sizeof(test::uxx) << endl;        // 8  
  19.     cout << sizeof(etest) << endl;   //4  
  20.       
  21.     enum etest et;          // 枚举类型变量,只能给枚举类型变量进行赋值枚举常量,不能给枚举常量(a,b,c,d,e)复制  
  22.     //cout << et << endl;    // 出错 使用初始化变量  
  23.     et = c;  
  24.     cout << et << endl;     // 2  
  25.     et = (enum etest)10;           //  必须进行强制转换  
  26.     cout << et << endl;     // 10  
  27.   
  28.     cout << sizeof(unsigned int) << endl;   // 4  
  29.     return 0;  
  30. }  
// // 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:

  1. class A   
  2.  {   
  3.       int i;   
  4.       union U   
  5.       {   
  6.           char buff[13];   
  7.           int i;   
  8.       }u;   
  9.       void foo() {    }   
  10.       typedef char* (*f)(void*);   
  11.       enum{red, green, blue} color;   
  12.  }a;   
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(a) = 24;   空函数是不占字节的,还有就是typedef只是一个声明而已,也不占字节;枚举类型是用int型实现的,故占4个字节

注意sizeof计算的是栈中分配的内存,因此当struct中存在static数据成员时,则不考虑在内。。。

  1. struct A{  
  2.     A(){};  
  3.     ~A(){};  
  4.     int m1;  
  5.     int m2;  
  6. };  
  7.   
  8. struct B:A{  
  9.     B(){}  
  10.     ~B(){}  
  11.     int m1;  
  12.     char m2;  
  13.     static char m3;  
  14.   
  15. };  
  16.   
  17. struct C{  
  18.     C(){}  
  19.     virtual~C(){}  
  20.     int m1;  
  21.     short m2;  
  22. };  
  23.   
  24.   
  25. int main(){  
  26.     cout << sizeof(A) << endl;   // 8  
  27.     cout << sizeof(B) << endl;    // 16  
  28.     cout << sizeof(C) << endl;     //12  
  29.     return 0;  
  30. }  
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;
}

其中B继承了A中已经有的8个字节,sizeof只计算栈中存储的变量,故不包含static中的变量大小,C中包含了一个指向虚表的指针。。。故为12


大小端模式


在C语言中除了8位的char型之外,还有16 的short型,32 的long型(要看具体的编译器),对于位数大于8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着如何将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。

大端模式:

字数据的高字节存储在低地址中,而字数据的低字节则存放在高地址中。

小端模式

与大端存储模式相反,在小端存储模式中,低地址中存放的是字数据的低字节,高地址存放的是字数据的高字节。

例如,16位宽的数0x1234在 小端模式CPU内存中的存放方式(假设从地址0x4000开始存放)为:

内存地址

0x4000

0x4001

存放内容

0x34

0x12

而在大端模式CPU内存中的存放方式则为:

内存地址

0x4000

0x4001

存放内容

0x12

0x34

32位宽的数0x12345678在小端模式CPU内存中的存放方式(假设从地址0x4000开始存放)为:

内存地址

0x4000

0x4001

0x4002

0x4003

存放内容

0x78

0x56

0x34

0x12

而在端模式CPU内存中的存放方式则为:

内存地址

0x4000

0x4001

0x4002

0x4003

存放内容

0x12

0x34

0x56

0x78


       我们常用的X86结构是小端模式,而KEIL C51则为大端模式。很多的ARM,DSP都为小端模式。有些ARM处理器还可以由硬件来选择是大端模式还是小端模式。

Note:采用大端方式进行数据存放符合人类的正常思维,而采用小端方式进行数据存放利于计算机处理。
(我的理解:小端模式在低字节就放一个低位)

下面这段代码可以用来测试一下你的编译器是大端模式还是小端模式:
int main()
{
short int x;
char x0,x1;
x=0x1122;

x0=*((char*)&x); //低地址单元 ,或者((char*)&x)[0];
x1=*((char*)&x + 1); //高地址单元,或者((char*)&x)[1];

printf("x0=%x\nx1=%x\n",x0,x1);
}

若x0=0x11,则是大端; 若x0=0x22,则是小端......


注意这里int *px = &x,, 那么px+1指向下一个short int数据,所以这里+1就加了2个字节=16位,而char *px = (char*)&x, px+1指向下一个char数据,这里+1就只有加了1个字节=8位

而如int a[4]; 那么&a+1这里+1实际是加了sizeof(int)*4个字节,因为这里的&a是指向数组的指针,是个数组指针,就是int(*b)[4],,这里的b就是&a,,, 而数组这个对象含有sizeof(int)*4个字节。。而int *b[4]; 这是一个指针数组,数组中每个元素都是int*即指针。。

  1. int main(int argc, char **argv)  
  2. {  
  3.     int a[4] = {1, 2, 3, 4};  
  4.     int *ptr = (int *)(&a + 1);  
  5.     printf("%d", *(ptr - 1));  
  6. }  
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)详解


【大端(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) — 低位

————–

低地址  

转自:http://blog.163.com/leng_zzu@126/blog/static/49955027201161210511469/
  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
在C语言中,结构体的字节对齐是为了优化内存访问速度和对齐要求而进行的一种对齐方式。结构体的字节对齐确保结构体中的成员按照一定的规则进行排列,以便于处理器高效地访问内存。 结构体的字节对齐规则通常由编译器根据特定的对齐选项和目标平台的要求来确定。在C语言中,可以使用`#pragma pack`指令或者编译器提供的特定选项来控制结构体的字节对齐方式。 默认情况下,大多数编译器会按照特定的对齐规则进行字节对齐。这些规则通常是根据基本数据类型的大小来确定的。例如,常见的对齐规则是按照4字节对齐(即结构体成员的偏移量必须是4的倍数)或者8字节对齐。 以下是一个示例,展示了如何使用`#pragma pack`指令来设置结构体的字节对齐方式: ```c #pragma pack(push, 1) // 以1字节对齐 struct MyStruct { char c; int i; double d; }; #pragma pack(pop) // 恢复默认的对齐方式 int main() { printf("sizeof(MyStruct) = %zu\n", sizeof(struct MyStruct)); return 0; } ``` 在上面的示例中,`#pragma pack(push, 1)`指令将当前的对齐方式推入一个栈中,并将对齐方式设置为1字节对齐。然后定义了一个包含不同类型成员的结构体。最后,使用`#pragma pack(pop)`指令将对齐方式恢复为默认值。 请注意,修改结构体的字节对齐方式可能会导致内存浪费或者访问错误,因此在修改字节对齐方式时要特别小心。建议仅在必要时进行修改,并确保了解目标平台的字节对齐要求。 希望这能回答你的问题!如果还有疑问,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值