- 网络字节序一般都是Big-endian。
- 主机字节序则需要查看设备的CPU架构:一般的有精简指令集的,常见的为ARM架构,比如手机,路 由器,交换机等,他们的字节序一般是可以手动配置的。还有一种架构是复杂指令集的,常见的为x86系列,比如PC机,这种一般都是Little-endian字节序的。我们公司用的是Big-endian的设备。
- Berkeley套接字API定义了一组函数用于16和32bit整数在网络序和主机字节序之间的转换。htonl,htons用于本机序转换到网络序;ntohl,ntohs用于网络序转换到本机序。
可惜记住这些,最后还是只能在用的时候死记这些,并不明白其中含义,所以在网上查阅了一些相关资料,增加了一点对于字节序的认识。
不看不知道,一看还蛮有趣的关于endian的来源:
- “endian”一词来源于乔纳森·斯威夫特的小说格列佛游记。小说中,小人国为水煮蛋该从大的一端(Big-End)剥开还是小的一端(Little-End)剥开而争论,争论的双方分别被称为Big-endians和Little- endians。
- 1980年,Danny Cohen在其著名的论文"On Holy Wars and a Plea for Peace"中,为平息一场关于字节该以什么样的顺序传送的争论,而引用了该词。
ps:大师们的涉猎真是广啊!
对于主机中的字节序,个人觉得其实字节序是指CPU从输入设备中获得数据写入到内存中的顺序。当然是对于该数据是大于设备内存的存储单位的(一般为8bites),如果一个数据是小于等于一个字节的,那它也就没有什么所谓的字节序了,内存中直接分配一个单位给它就能储存它所表示的值了。
(ps:上述的想麻烦牛人们正确讲解一下)
看下面的代码:
-
#include <stdio.h>
-
#include <stdlib.h>
-
-
-
int main(void)
-
{
-
union
-
{
-
int i;
-
char x[5];
-
}a;
-
-
a.x[0] = 5;
-
a.x[1] = 4;
-
a.x[2] = 3;
-
a.x[3] = 2;
-
a.x[4] = 1;
-
-
-
printf("\n a.i = %x", a.i); /* 这里i 只有四个字节,所以没有最低位的a.x[4] */
-
printf("\n a.i = %p, a.x=%p", &a.i, &a.x);
-
printf("\n a.x[3] = %p, a.x[4] = %p, sizeof(a)= %d", &a.x[3], &a.x[4], sizeof(a));
-
-
return 0;
- }
运行结果:
从运行结果中我们可以看到,在union 结构 a 中 i 和 x的内存地址是一样的,这个符合一般我们对于union 的理解。数组x中数据存储在内存中也是按照写入的顺序从低地址到高地址储存。(如结果中的x[3],和x[4]的地址可以看出,因为char是一个字节的,所以存储时不关心字节序)。而 最终 i 输出的值是多少则是由主机的cpu到底属于哪个架构决定的。(本台机器是x86的,是Little-endian)
这里先了解一下在大小端的情况下到底CPU是按照怎么样的顺序读取内存中的数据:
先认识一下基本的知识,关于高低字节和高低位,
高低位是相对于内存中数据存储的地址的高低,如下图:(ps:摘自网络32byte)
- ----------------------- 最高内存地址 0xffffffff
- | 栈底
- .
- . 栈
- .
- 栈顶
- -----------------------
- |
- |
- /|/
- NULL (空洞)
- /|/
- |
- |
- -----------------------
- 堆
- -----------------------
- 未初始化的数据
- ----------------(统称数据段)
- 初始化的数据
- -----------------------
- 正文段(代码段)
- ----------------------- 最低内存地址 0x00000000
这里如上例中的a.x[5],由于x[5]是由栈已分配好的,所以x[4],x[3],x[2],x[1],x[0]已经在栈中有各自的地址:
栈底 (高地址)
----------
x[4]
x[3]
x[2]
x[1]
x[0]
----------
栈顶 (低地址)
这个可以从运行结果得到验证。
高低字节则是指例如一个数 int a = 0x01020304,那么高字节到低字节依次是1 2 3 4.
了解了高低位和高低字节后看下面的例子
例子是WIKI百科中的一个例子:
以十六字节的数据为例:0A0B0C0D (该数据对应的是从高地址地址到低地址,即0A是高地址,0D是低地址)
大端:
存储单位为8bites
地址增长方向 → | |||||
... | 0x0A | 0x0B | 0x0C | 0x0D | ... |
储存单位为16bites
increasing addresses → | |||||
... | 0A0Bh | 0C0Dh | ... |
可以看到大端字节序把0A0B0C0D存储到内存中是把高字节的0A储存在内存中的低地址位,而低字节0D则是在高地址位。
小端字节序:
储存单位为8bites
increasing addresses → | |||||
... | 0Dh | 0Ch | 0Bh | 0Ah | ... |
存储单位为16bites
increasing addresses → | |||||
... | 0C0Dh | 0A0Bh | ... |
很明显可以看出此时小端字节序和大端正好相反,把高字节的0A放在内存的高地址位,低字节的0D放在低地址位。
在了解了下字节序后,可以来解决这个最初代码中的那个x.i的值的问题了:
union中 i 和 x 是共用一段内存区域的,所以它们的首地址是一样的。
x[5] 在内存中的分布是
栈底 (高地址)
----------
x[4] 01 x[3] 02
x[2] 03
x[1] 04
x[0] 05
----------
栈顶 (低地址)
由于都是一个字节的所以没有所谓的字节序。当要通过a.i 取值时,由于i 是int类型占四个字节,所以相应的 i 会取从起始位置x[0]开始的四个字节依次是 05 04 03 02 。由于是小端字节序,所以应该高地址位对于高字节,所以 i 的值是 i = 2030405;(十六进制表示)。
感觉这个例子不好理解,其实应该修改程序,把给x[5]赋值换成给i 赋值更能直接的表达小端字节序是怎么存储的。
修改代码:
-
#include <stdio.h>
-
#include <stdlib.h>
-
-
-
int main(void)
-
{
-
union
-
{
-
int i;
-
char x[5];
-
}a;
-
-
#if 0
-
a.x[0] = 5;
-
a.x[1] = 4;
-
a.x[2] = 3;
-
a.x[3] = 2;
-
a.x[4] = 1;
-
#endif
-
-
a.i = 0x02030405;
-
printf("\n a.i = %x", a.i); /* 这里i 只有四个字节,所以没有最低位的a.x[4] */
-
printf("\n a.i = %p, a.x=%p", &a.i, &a.x);
-
printf("\n a.x[0] = %x, a.x[1] = %x, a.x[2] = %x, a.x[3] = %x, a.x[4] = %x,sizeof(a)= %d", a.x[0], a.x[1], a.x[2], a.x[3], a.x[4], sizeof(a));
-
-
return 0;
- }
运行结果为:
有兴趣可以分析,理解一下这个结果。
有关字节序可以查看wiki中的文章:
英文版:
中文版:
ps: 希望有理解错误的地方能够帮忙指正!
添加一个实例:
试题1:请写一个C函数,若处理器是Big_endian的,则返回0;若是Little_endian的,则返回1
解答:
int checkCPU()
{
{
union w
{
int a;
char b;
} c;
c.a = 1;
return (c.b == 1);
}
}
剖析:
嵌入式系统开发者应该对Little-endian和Big-endian模式非常了解。采用Little-endian模式的CPU对操作数的存放方 式是从低字节到高字节,而Big-endian模式对操作数的存放方式是从高字节到低字节。例如,16bit宽的数0x1234在Little- endian模式CPU内存中的存放方式(假设从地址0x4000开始存放)为:
内存地址 存放内容
0x4000 0x34
0x4001 0x12
而在Big-endian模式CPU内存中的存放方式则为:
内存地址 存放内容
0x4000 0x12
0x4001 0x34
32bit宽的数0x12345678在Little-endian模式CPU内存中的存放方式(假设从地址0x4000开始存放)为:
内存地址 存放内容
0x4000 0x78
0x4001 0x56
0x4002 0x34
0x4003 0x12
而在Big-endian模式CPU内存中的存放方式则为:
内存地址 存放内容
0x4000 0x12
0x4001 0x34
0x4002 0x56
0x4003 0x78
联合体union的存放顺序是所有成员都从低地址开始存放,面试者的解答利用该特性,轻松地获得了CPU对内存采用Little-endian还是Big-endian模式读写。