Chinaunix首页 | 论坛 | 博客
  • 博客访问: 812043
  • 博文数量: 157
  • 博客积分: 542
  • 博客等级: 中士
  • 技术积分: 1696
  • 用 户 组: 普通用户
  • 注册时间: 2011-11-21 20:21
文章分类
文章存档

2017年(1)

2016年(2)

2015年(6)

2014年(42)

2013年(77)

2012年(19)

2011年(10)

分类: C/C++

2011-12-13 16:48:34

   做嵌入式,由于做的是上层的路由协议,感觉和底层接触的太少了,以至于时常会被对字节序玩弄, 搞的头脑昏昏,最后由于并没有接触,以至于只是记了下这些:
  •   网络字节序一般都是Big-endian。
  •   主机字节序则需要查看设备的CPU架构:一般的有精简指令集的,常见的为ARM架构,比如手机,路 由器,交换机等,他们的字节序一般是可以手动配置的。还有一种架构是复杂指令集的,常见的为x86系列,比如PC机,这种一般都是Little-endian字节序的。我们公司用的是Big-endian的设备。
  •   Berkeley套接字API定义了一组函数用于16和32bit整数在网络序和主机字节序之间的转换。htonl,htons用于本机序转换到网络序;ntohl,ntohs用于网络序转换到本机序。

可惜记住这些,最后还是只能在用的时候死记这些,并不明白其中含义,所以在网上查阅了一些相关资料,增加了一点对于字节序的认识。

不看不知道,一看还蛮有趣的关于endian的来源:

  1. “endian”一词来源于乔纳森·斯威夫特的小说格列佛游记。小说中,小人国为水煮蛋该从大的一端(Big-End)剥开还是小的一端(Little-End)剥开而争论,争论的双方分别被称为Big-endians和Little- endians。
  2. 1980年,Danny Cohen在其著名的论文"On Holy Wars and a Plea for Peace"中,为平息一场关于字节该以什么样的顺序传送的争论,而引用了该词。

ps:大师们的涉猎真是广啊!

对于主机中的字节序,个人觉得其实字节序是指CPU从输入设备中获得数据写入到内存中的顺序。当然是对于该数据是大于设备内存的存储单位的(一般为8bites),如果一个数据是小于等于一个字节的,那它也就没有什么所谓的字节序了,内存中直接分配一个单位给它就能储存它所表示的值了。

(ps:上述的想麻烦牛人们正确讲解一下)

看下面的代码:

  1. #include <stdio.h>
  2. #include <stdlib.h>


  3. int main(void)
  4. {
  5.   union
  6.   {
  7.     int i;
  8.     char x[5];
  9.   }a;
  10.   
  11.   a.x[0] = 5;
  12.   a.x[1] = 4;
  13.   a.x[2] = 3;
  14.   a.x[3] = 2;
  15.   a.x[4] = 1;
  16.   
  17.   
  18.   printf("\n a.i = %x", a.i); /* 这里i 只有四个字节,所以没有最低位的a.x[4] */
  19.   printf("\n a.i = %p, a.x=%p", &a.i, &a.x);
  20.   printf("\n a.x[3] = %p, a.x[4] = %p, sizeof(a)= %d", &a.x[3], &a.x[4], sizeof(a));
  21.   
  22.   return 0;
  23. }

运行结果:

从运行结果中我们可以看到,在union 结构 a 中 i 和 x的内存地址是一样的,这个符合一般我们对于union 的理解。数组x中数据存储在内存中也是按照写入的顺序从低地址到高地址储存。(如结果中的x[3],和x[4]的地址可以看出,因为char是一个字节的,所以存储时不关心字节序)。而 最终 i 输出的值是多少则是由主机的cpu到底属于哪个架构决定的。(本台机器是x86的,是Little-endian)

这里先了解一下在大小端的情况下到底CPU是按照怎么样的顺序读取内存中的数据:

先认识一下基本的知识,关于高低字节和高低位,

高低位是相对于内存中数据存储的地址的高低,如下图:(ps:摘自网络32byte)

  1. ----------------------- 最高内存地址 0xffffffff
  2. | 栈底
  3. .
  4. . 栈
  5. .
  6. 栈顶
  7. -----------------------
  8. |
  9. |
  10. /|/
  11. NULL (空洞)
  12. /|/
  13. |
  14. |
  15. -----------------------
  16. -----------------------
  17. 未初始化的数据
  18. ----------------(统称数据段)
  19. 初始化的数据
  20. -----------------------
  21. 正文段(代码段)
  22. ----------------------- 最低内存地址 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 赋值更能直接的表达小端字节序是怎么存储的。

修改代码:

 

  1. #include <stdio.h>
  2. #include <stdlib.h>


  3. int main(void)
  4. {
  5.   union
  6.   {
  7.     int i;
  8.     char x[5];
  9.   }a;
  10.   
  11. #if 0
  12.   a.x[0] = 5;
  13.   a.x[1] = 4;
  14.   a.x[2] = 3;
  15.   a.x[3] = 2;
  16.   a.x[4] = 1;
  17. #endif

  18.   a.i = 0x02030405;
  19.   printf("\n a.i = %x", a.i); /* 这里i 只有四个字节,所以没有最低位的a.x[4] */
  20.   printf("\n a.i = %p, a.x=%p", &a.i, &a.x);
  21.   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));
  22.   
  23.   return 0;
  24. }

运行结果为:

有兴趣可以分析,理解一下这个结果。

有关字节序可以查看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模式读写。


阅读(2550) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~