字节序是由于不同的主处理器和操作系统,对大于一个字节的变量在内存中的存放顺序不同而产生的,例如2个字节的short int和4个字节的int类型变量都有字节序的问题。
大端字节序和小端字节序
字节序是由于CPU和OS对多字节变量的内存存储顺序不同而产生的。
字节序介绍
例如一个16位的整数,它由两个字节构成,它由两个字节构成,在有的系统上会将高字节序放在内存的低地址上,而有的系统上则高字节放在内存的高地址上,所以存在字节序的问题。大于一个字节的变量类型的表示方法有以下方法。
- 小端字节序(Listen Endian, LE):在表示变量的内存地址的起使地址存放低字节,高字节顺序存放;
- 大端字节序(Big Endian, BE):在表示变量的内存地址的起使地址存放高字节,低字节顺序存放。
例如变量的值为0xabcd,在大端字节序和小端字节序的系统中二者的存放顺序是不同的,在小端字节序系统中的存放顺序如下图所示,假设存放值0xabcd的内存地址的起使地址为0,则0xabcd在地址15-8的地址上,而0xcd在地址7-0的位置上。在大端字节序系统中的存放顺序如图所示,假设存放值0xabcd的内存地址起使地址为0,则0xab在地址7-0的地址上,而0xcd在地址15-8的位置上。
图中表示的是一个16b的变量的字节序交换方法。在小端字节序主机系统中,进行转换时,将高地址的字节和低地址的字节进行交换;图中表示的是一个32b的变量进行字节序交换的方法,在小端字节序主机系统中,进行字节序交换时,第0个字节的值与第3个字节的值进行交换,第1个字节的值与第2个字节的值进行交换。
字节交换的作用是生成一个网络字节序的变量,其字节的顺序与主机类型和操作系统无关。进行网络字节序转换的时候,只要转换一次就可以了,不要进行多次的转换。如果进行多次字节序的转换,最后生成的网络字节序的值可能是错误的。例如,对于主机为小端字节序的系统,进行两次字节序转换的过程如下图所示,经过两次转换,最终的值与最初的主机字节序相同。
字节序的例子
下面的一段代码用于检查上图所示变量在内存中的表示方法,确定系统中的字节序为大端字节序还是小端字节序。
(1)字节序结构。程序先建立一个联合类型to,用于测试字节序,成员value是short类型变量,可以通过成员byte来访问value变量的高字节和低字节。
#include <stdio.h>
/*联合类型的变量类型,用于测试字节序;
*成员value的高低端字节可以由成员type按字节访问
*/
typedef union{
unsigned short int value; //短整型变量
unsigned char byte[2]; //字符类型
}
(2)变量类型。声明一个to类型的变量typeorder,将值0xabcd赋给成员变量value。由于在类型to中,value和byte成员共享一块内存,所以可以通过byte的不同成员来访问value的高字节和低字节。
int main(int argc, char *argv[])
{
to typeorder; //一个to类型变量
typeorder.value = 0xabcd; //将typeorder变量赋值为0xabcd
}
(3)小端字节序判断。小端字节序的检查通过判断typeorder变量的byte成员高字节和低字节的值来进行:低字节的值为0xcd,高字节的值为0xab。
/*小端字节序检查*/
if(typeorder.byte[0] ==0xcd && typeorder.byte[1] ==0xab){
printf("Low endian byte order"
"byte[0]:0x%x,byte[1]:0x%x\n",
typeorder.byte[0],
typeorder.byte[1]);
}
(4)大端字节序判断。大端字节序的检查同样通过判断typeorder变量的byte成员高字节和低字节的值来进行:低字节的值为0xab,高字节的值为0xcd。
//大端字节序检查
if(typeorder.byte[0] == 0xab && typeorder.byte[1] == 0xcd){
//高字节在前
printf("High endian byte order"
"byte[0]:0x%x,byte[1]:0x%x\n",
typeorder.byte[0],
typeorder.byte[1]);
}
return 0;
}
(5)编译运行程序。将上面的代码保存到check_order.c文件中,对文件进行编译后运行,得出如下结果。
$ gcc -o check_order check_order.c
$ ./check_order
Low endian byte orderbyte[0]:0xcd,byte[1]:0xab
在笔者的系统上,值0xabcd在系统的表达方式为0xab在后,0xcd在前,所以系统是小端字节序。
一个字节序转换的例子
下面的例子是对16位数值和32位数值进行字节序转换,每种类型的数值进行两次转换,最后打印结果。
16位字节序转换结构
先定义用于16位字节序转换的结构to16,这个结构是一个联合类型,通过value来赋值,通过byte数组来进行字节序转换。
#include <stdio.h>
#include <arpa/inet.h>
//联合类型的变量类型,用于测试字节序
//成员value的高低端字节可以由成员type按字节访问
//16位字节序转换的结构
typedef union{
unsigned short int value; //16位short类型变量value
unsigned char byte[2]; //char类型数组,共16位
}
32位字节序转换结构
用于32位字节序转换的结构名称位to32,与to16类似,它也有两个成员变量:value和byte。成员变量value是一个unsigned long int类型的变量,32位长;成员变量byte是一个char类型的数组,数组的长度是4,也是32位长。32位字节序的转换可以通过to32的value成员变量来赋值,通过byte来进行字节序的转换。
//32位字节序转换的结构
typedef union{
unsigned long int value; //32位unsigned long类型变量
unsigned char byte[4]; //char类型数组,共32位
}
变量值打印函数showvalue()
showvalue()函数用于打印变量值,打印的方式是从变量存储空间的第一个字节开始,按照字节进行打印。showvalue()函数有两个输入参数,一个是变量的地址指针begin,另一个是字长的标志flag。参数flag的值为BITS16的时候打印16位变量的值,参数flag为BITS32的时候打印32位变量的值。
#define BITS16 16 //常量,16
#define BITS32 32 //常量,32
/*按照字节打印,begin为字节开
*flag为BITS16表示16位
*flag位BITS32表示32位
*/
void showvalue(unsigned char *begin, int flag)
{
int num = 0,i = 0;
if(flag == BITS16){ //一个16b的变量
num = 2;
}else if(flag == BIST32){
num = 4;
}
for(i=0; i<num; i++) //显示每个字节的值
{
printf("%x ",*(begin+i));
}
printf("\n");
}
主函数main()
主函数main()中,先定义用于16位字节序变量转换的变量v16_orig、v16_turn1、v16_turn2,其中v16_ori是16位变量的原始值,v16_turn1是16位变量进行第一次字节序转换后的结果,v16_turn2是16位变量进行第二次字节序转换后的结果(即对变量v16_turn1进行一次字节序转换)。同时定义了用于32位字节序变量转换的变量v32_orig、v32_turn1、v32_turn2,其中v32_orig是32位变量的原始值,v32_turn1是32位变量进行字节序转换后的结果,v32_turn2是32位变量进行第二次字节序转换后的结果(即对变量v32_turn1进行一次字节序转换)。
int main(int argc, char *argv[])
{
to16 v16_orig, v16_turn1, v16_turn2; //一个to16类型变量
to32 v32_orig, v32_tur1, v32_turn2; //一个to32类型变量
}
16位0xabcd的二次转换
给16位变量赋初始值0xabcd,然后进行第一次字节序转换,并将结果赋给v16_turn1;进行第二次字节序转换的方式是对v16_turn1进行一次字节序转换。
v16_orig.value = 0xabcd; //赋值为0xabcd
v16_turn1.value = htons(v16_orig.value); //第一次转换
v16_turn2.value = htons(v16_turn1.value); //第二次转换
32位0x12345678的二次转换
给32位变量赋初始值0x12345678,然后进行第一次字节序转换,并将结果赋给v32_turn1;进行第二次字节序转换的方式是对v32_turn1进行一次字节序转换。
v32_orig.value = 0x12345678; //赋值为0x12345678
v32_turn1.value = htons(v32_orig.value); //第一次转换
v32_turn2.value = htons(v32_turn1.value); //第二次转换
结果打印
最后将16位变量进行两次字节序转换的结果和32位变量 进行两次字节序转换的结果打印出来
//打印结果
printf("16 host to network byte order change:\n");
printf("\torig:\t");showvalue(v16_orig.byte, BITS16);
//16位数值的原始值
printf("\t1 times:");showvalue(v16_turn1.byte, BITS16);
//16位数值的第一次转换后的值
printf("\t2 times:");showvalue(v16_turn2.byte, BITS16);
//16位数值的第二次转换后的值
printf("32 host to network byte order change:\n");
printf("\torig:\t");showvalue(v32_orig.byte,BITS32);
//32位数值的原始值
printf("\t1 times:");showvalue(v32_turn1.byte, BITS32);
//32位数值的第一次转换后的值
printf("\t2 times:");showvalue(v32_turn2.byte, BITS32);
//32位数值的第二次转换后的值
return 0;
}
16位变量0xabcd在内存中的表示方式为cd在前,ab在后;进行一次字节序转换后变为ab在前,cd在后;进行第二次字节序转换后变为cd在前,ab在后。上面的情况是在笔者的小端字节序系统上的结果,在大端字节序的主机上,即使调用字节序转换函数,字节序也不会发生变化。同时可以发现,在进行第一次转换后字节序发生了变化,而进行第二次字节序转换后与原始的排列方式一致。
将上述程序中进行第二次转换的主机向网络字节序转换的函数,替换成网络字节序向主机字节序转换的函数,即htons()替换成ntohs(),htonl()替换成ntohl(),结果如下:
与不替换的情况完全一致,从结果看好像htons()和ntohs()、htonl()和ntohl()这两个函数没有什么区别。其实在很多平面上htons()和ntohs()、htons()和mtohl()是完全一致的,因为这些函数的本质就是进行字节序的转换。