字节序与字节对齐

一.网络字节序与主机字节序

1.大端和小端存储
大端(Big Endian):高位存低地址。符合人类的正常思维。网络字节序采用大端(网络传输的是字节流)。
 小端(Littile Endian):低位存低地址。

如果将一个32位的整数0x12345678存放到一个整型变量(int)中,这个整型变量采用大端或者小端模式在内存中的存储由下表所示。
---------------------------
地址偏移 大端模式 小端模式
0x00      12     78
0x01      34     56
0x02      56     34
0x03      78     12
---------------------------

如果将一个16位的整数0x1234存放到一个短整型变量(short)中。这个短整型变量在内存中的存储在大小端模式由下表所示。
---------------------------
地址偏移 大端模式 小端模式
0x00      12     34
0x01      34     12
---------------------------

例:
int main(int argc,char** argv)
{
  int num = 0x12345678;
  unsigned char* pc = (unsigned char*)(&num);
  printf("local order:\n");
  printf("[0]: 0x%X addr:%u\n", pc[0], &pc[0]);
  printf("[1]: 0x%X addr:%u\n", pc[1], &pc[1]);
  printf("[2]: 0x%X addr:%u\n", pc[2], &pc[2]);
  printf("[3]: 0x%X addr:%u\n", pc[3], &pc[3]);
  num = htonl(num);
  printf("htonl order:\n");
  printf("[0]: 0x%X addr:%u\n", pc[0], &pc[0]);
  printf("[1]: 0x%X addr:%u\n", pc[1], &pc[1]);
  printf("[2]: 0x%X addr:%u\n", pc[2], &pc[2]);
  printf("[3]: 0x%X addr:%u\n", pc[3], &pc[3]);
  return 0;
}
SPARC平台上的输出:
local order:
[0]: 0x12 addr:4290770212 //高位字节存放在低地址处,则是大端法;
[1]: 0x34 addr:4290770213
[2]: 0x56 addr:4290770214
[3]: 0x78 addr:4290770215 //低位字节存放在高地址处;
htonl order:
[0]: 0x12 addr:4290770212 //由此看出,主机字节序与网络字节一样;
[1]: 0x34 addr:4290770213
[2]: 0x56 addr:4290770214
[3]: 0x78 addr:4290770215
X86平台上的输出:
local order:
[0]: 0x78 addr:4289157020 //低位字节存放在低地址处,则是小端法;
[1]: 0x56 addr:4289157021
[2]: 0x34 addr:4289157022
[3]: 0x12 addr:4289157023 //高位字节存放在高地址处;
htonl order:
[0]: 0x12 addr:4289157020 //由此看出,主机字节序与网络字节不一样;
[1]: 0x34 addr:4289157021
[2]: 0x56 addr:4289157022
[3]: 0x78 addr:4289157023

2.字节序转化使用htons()还是使用htonl()?还是两者都不行?
首先这两个函数不是随便使用的,单字节数据无需也不能转化,2字节数据只能使用htons()转换,4字节数据只能使用htonl()
原因:例子:对于2个short数据0x1234和0x5678,存储如下
---------------------------
地址偏移 大端模式
0x00      12    
0x01      34   
0x02      56   
0x03      78   
如果使用long 转化函数。发送端假如为大端,则0x1234 0x5678使用htonl()转化成网络字节序后,任然为0x1234 0x5678;
接收端如果为小端,使用ntohl()转化后变为0x5678,0x1234这两个数据交换了位置。
---------------------------
地址偏移   小端模式
0x00         78
0x01         56    小端0x5678
0x02         34
0x03         12    小端0x1234
---------------------------
所以2字节和4字节的转换不能混用。那么问题来了,8字节的double,怎么转换?
解答:如果当前主机为小端,接收时先使用ntohl()转换前4字节,再使用ntohl()转换后4字节,然后交换前后4字节。

在使用socket传输类于T_WfsTpMsg结构体的数据时,msg部分由于转换时不确定数据类型,给转换带来了难度,所以一定要定义好msg的数据类型,方便转换。msg最好能是字符。
转化错了会存在倒序的问题。

3.字节对齐对字节序的影响
如结构体
#pragma pack(4)
typedef struct {
 char a;
 int  b;
}AM;

void main(void)
{
 AM a={1,2};
 unsigned char *p=(unsigned char *)(&a);
 int i=0;
 while(i<8)
 { 
  printf("%4X",(INT)p[i]);
  cout<<endl;
  i++;
 }
}
打印该结构体数据保存为:(大端)
   1
  CC 
  CC
  CC
   2
   0
   0
   0
htonl(*((int *)(&a)))==30198988==0x01CCCCCC !=1
所以如果使用&AM指针,像msg一样用htonl()来转化,那么肯定错了。msg申明了#pragma pack(1),这样使得数据的存储是紧凑的,虽然可以规避该问题,但那样不分数据类型直接全部使用htonl()转换,任然存在一些问题。


二、字节对齐

如何修改编译器的默认对齐值?
1.在VC IDE中,可以这样修改:[Project]|[Settings],c/c++选项卡Category的Code Generation选项的Struct Member Alignment中修改,默认是8字节。
2.在编码时,可以这样动态修改:#pragma pack(2) .注意:是pragma而不是progma.

字节对齐可能带来的隐患:代码中关于对齐的隐患,很多是隐式的。比如在强制类型转换的时候。例如:
unsigned int i = 0x12345678;
unsigned char *p=NULL;
unsigned short *p1=NULL;

p=&i;
*p=0x00;
p1=(unsigned short *)(p+1); //在有些CUP的体系结构,不支持字节非对齐访问。
*p1=0x0000;
最后两句代码,从奇数边界去访问unsignedshort型变量,显然不符合对齐的规定。
在x86上,类似的操作只会影响效率,但是在MIPS或者sparc上,可能就是一个error,因为它们要求必须字节对齐.

如何查找与字节对齐方面的问题:

如果出现对齐或者赋值问题首先查看
1. 编译器的big little端设置
2. 看这种体系本身是否支持非对齐访问
3. 如果支持,看设置了对齐与否,如果没有则看访问时需要加某些特殊的修饰来标志其特殊访问操作。

 

结构体所占用的存储单元数的计算(即使用sizeof所计算出来的大小)-
字节对齐:即各个数据类型的地址从哪里开始,以及占多少个存储单元。
2个定理:(解决所有问题)
 同步定理:结构体自身和有效对齐与最大成员同步 
 整数倍定理:结构体或普通数据类型的起始内存地址为有效对齐字节的整数倍,并且结构体或普通数据类型所占字节数一定是其有效对齐字节的整数倍

1.首先结构体中的3个对齐概念: 
 数据类型自身对齐:32位系统中char 1,short 2,int 4,long 4,float 4,double 8
 数据类型指定对齐:使用#pragma pack(x) 指定的系统使用x字节对齐方式。
 数据类型有效对齐:数据类型自身对齐与指定对齐中较小者作为数据类型的有效对齐字节数。
 同步定理:结构体自身和有效对齐与最大成员同步 
2.各个数据类型所占内存的字节数计算
 #pragma pack(x)
 char:起始地址(有效对齐)为1和x中较小者,占1个字节
 short: 起始地址(有效对齐)为2和x中较小者,占2个字节
 int: 起始地址(有效对齐)为4和x中较小者,(如果x=2,则地址位于2的整数倍位置),占4个字节
 double: 起始地址(有效对齐)为8和x中较小者,(如果x=4,则地址位于4的整数倍位置),占8个字节 
  整数倍定理:结构体或普通数据类型的起始内存地址为有效对齐字节的整数倍,并且结构体或普通数据类型所占字节数一定是其有效对齐字节的整数倍

3.结构体所占字节数计算
 分3步计算:
 (1)分别计算各个成员所占的内存空间(这个对于简单数据类型int/char等是固定的,但对于结构体需要计算得出)。
 (2)以初始地址0为坐标,按有效对齐(起始地址),依次放置各个成员;计算第一个成员到最后一个成员的内存长度。
 (3)根据定理(结构体所占字节数一定是其有效对齐字节的整数倍),补齐内存长度,即为最后结构体所占的内存长度。
 
   以struct A{int a;char b;short c;char d;};结构体为例#pragma pack(4)
   1.各成员所在内存地址及所占字节(该计算与结构体无关):int 0:4;char 4,1;short 6:2,char 8:1   一共占了9个地址空间
   2.结构体有效对齐与成员中自身对齐最大的成员的有效对齐相同为:成员中最大的为4,指定对齐为4,故有效对齐为4(两者中较小者),所以最后结构体所占的内存空间一定是4的整数倍
   3.所以结构体所占的字节数为 (9/4+1)*4=12   ,上述结构体与struct A{int a;char b;short c;char d;char e;char f;};所占的空间是一样的。
  
   计算结构体大小时回答3个问题:
   1.各成员占几个字节
   2.各成员起始地址从哪里开始(由有效对齐值决定)
   3.结构体所占空间是几的整数倍
   struct A{  //#pragma pack(4)
 int a; //0:4
 char b;//4:1
 short c;//6:2
 char d;//8:1
 int  e;//12:4
 char f://16:1  最后占了17个字节,结构体所占空间是4的整数倍,故该结构体占20个字节
};
   struct A{  //#pragma pack(2)
 int a; //0:4
 char b;//4:1
 short c;//6:2
 char d;//8:1
 int  e;//10:4
 char f://14:1  最后占了15个字节,结构体所占空间是2的整数倍,故该结构体占16个字节
};
   struct A{  //#pragma pack(1)
 int a; //0:4
 char b;//4:1
 short c;//5:2
 char d;//7:1
 int  e;//8:4
 char f://12:1  最后占了13个字节,结构体所占空间是1的整数倍,故该结构体占13个字节
};


本文出自 “tech记录” 博客,谢绝转载!

你可能感兴趣的:(网络,主机)