织梦CMS - 轻松建站从此开始!

罗索

C语言综合--数字字节序

jackyhwei 发布于 2010-11-04 15:46 点击:次 
计算机的内存是一维的线性空间,是没有左右之分,只有地址高址,低址之分.在32bit CPU下,最低为0x0,最高为0xFFFFFFFF.但是在一般调试软件或者文档之中,如果内存横着描述,一般把低址列左侧,高址位于右侧.
TAG:

一.整位数字存储

     C语言的开发者都知道,32位CPU下,一个整数用4个字节存储的.(即sizeof(unsigned int)==4).一个short型用2字节存储(即sizeof(unsigned short)==2)

      那整数在4个byte的内存空间是如何保存的? 首先我们从小学学数学就有一个观念,比如一个整数数字 12345, 数字最高位在最左边,数字最低位在最右边.在C语言编程中也是遵循这样的习惯.比如数字左移,表示数字向左边移动,即低位向高位移动.进位也是由右至左依次进行.在C语言里,把一个数的最高位为MSB(最高有效字节),而一个数的最低位称为LSB(最低有效位).

      而计算机的内存是一维的线性空间,是没有左右之分,只有地址高址,低址之分.在32bit CPU下,最低为0x0,最高为0xFFFFFFFF.但是在一般调试软件或者文档之中,如果内存横着描述,一般把低址列左侧,高址位于右侧.如VC++中的描述.

 
一般人的直觉是,数字在内存的存储应该是 数字的高位(MSB)在低址上,低位(LSB)在高址上.这样在内存中,数字就是按人类的数学习惯的方法展开.
以数字0x12345678为例,人们可能认为它在内存表示是
 

 

但是我们在Windows 或X86 Linux 测试如下程序.看一下输出结果是

/* Author:Andrew Huang bluedrum@163.com */

#include <stdio.h>

void test1()
{
  unsigned long n = 0x12345678;

   unsigned char * ch = (unsigned char *)&n;

   printf("n=0x%x (0x%x,0x%x,0x%x,0x%x)\n",n,ch[0] & 0xFF, ch[1] & 0xFF,ch[2] & 0xFF,ch[3] & 0xFF);

}

int main()
{
  test1();
}

 

程序的输出是 n=0x12345678 (0x78,0x56,0x34,0x12),换句话说,这个数字并不是想象那样,是从低址顺排过来,而且反着排列的.即在内存中是按如下排列的.


 

为什么会这样呢?这要从CPU的硬件设计说起.CPU的发展过程中,形成了两种流派,一种是数字在内存中是顺排的.即看起来象一般数学表达式,是高字节数据(MSB)存放在低地址处,低字节数据(LSB)存放在高地址处。这种方式叫大端字节序,(big-endian),大部分的嵌入式CPU,象 PowerPC,

  另外一种是X86的CPU为代表的,数字在内存中是反排,即低字节数据(LSB)存放在内存低地址处,高字节数据(MSB)存放在内存高地址处,这种方式叫小端字节序,(little-endian),

ARM CPU则可以用软件在启动时设置采用哪一种字节序.但是运行时只能有一种字节序.

两种字节序各有优缺点,大端模式合符人的思维,小端模式合适计算机的处理.效率上并没有差别.只是设计者的喜好.这正好跟这两个术语的来源有一点相似.

   端模式(Endian)的这个词出自Jonathan Swift书写的著名童话《格列佛游记》。在这本书里,小人国根据将鸡蛋敲开的方法不同将所有的人分为两类,从圆头(较大那头)开始将鸡蛋敲开的人被归为Big Endian,从尖头(较小的那头)开始将鸡蛋敲开的人被归为Littile Endian。大家都觉得自己吃法是最完美的,最后争执不下.最后小人国的爆发了激列的内战,两派人两败俱伤,最后还是归于原状,little-endian仍然从小头开始吃起,big-endian还是从大头吃起.
    在CPU的设计上,Endian表示数据在存储器中的存放顺序。对于Big Endian和Little Endian不同的设计也引起了激烈的争论。两者也是各执一词,不过X86占有市场太多,因此Little-Endian略占上风.

二.数字字节序的应用
 
    即然数字字节序这么重要,为什么很多C或C++开发者没有意识到这个问题? 这主要是因为在C语言的数学运算中,编译器和硬件屏蔽这一细节.比如数字左移操作,比如开发者都可以认为是从数字低位向高位移动,至于移动后,数字存储格式如何调整,CPU会处理其中细节.
   换句话说,数字的表达式运算是不需要关心字节序的.但是如果牵涉到数字的存储就会牵涉到数字字节序.如上例把整数转换为字符数组,就要考虑字节序问题.
   需要考虑数字字节序数据类型包括2个Byte或以上的字节来表示的整数类型,因此char类型一般不需要数字字节序问题.一般只考虑short和int,long型,在64bit CPU下还要考虑64bit长整型的数字字节序.
 
   需要考虑数字字节序的场合有:
  
   1.二进制文件存储数字.(想想你在big-endian下存一个数字,到little-endian直接打开会有什么后果)
  2.网络上传输数字时。
  3.底层寄存器的设置。
  4.unicode 编码的存储。(一个unicode 是两个byte的类形。因此保存一个unicode编码的,也会因字节序不同有两种格式,unicode 默认用little-endian来存储,还有一种叫unicode-be.表示用big-endian来存储。
 
  
我们来看看如下两个关于字节序的问题。
1。如何编程来判断CPU是哪一种字节序。
  

int is_little_endian()
{
   unsigned int n = 0x01;

   return (*(char *)&n);
}

解答,0x01在little-endian是反排,第一个byte必然是0x01,反之是 0x00.。
 
 
2.在win32 下,下列程序输出什么结果?
   
 

#include <stdio.h>
union u1{
  unsigned long a;
  unsigned char b[4];
};

int main()
{
  union u1 u;
  u.a= 0x58739848;
  printf("0x%x\n",u.b[3]&0xFF);
}

解答:这是一个典形在考你对数字字节序的理解,WIN32暗示你是little endian.又因为union,所以 a,b 共享同一空间。这样b[3]是a的最高2位。因此结果是 0x58

 

3.在little-endian,下列程序输出结果是多少?

void test4()
{
     unsigned long a = 0x58739848;
    
     printf("0x%x\n", a & 0xFF);
}

解答,这个题可能在没学数字字节序之前,可能都能答对。学了后,反而搞混了,在想little-endian是反排,这样 & 0xFF,应该是取最高位,很容易答成了 0x58,实际上&的操作与数字字节序无关,总是取数字最低一byte.在哪种CPU下,上述答案都是 0x48

三.网络字节序,本机字节序

   IP协议是定义在可以在任何操作系统或CPU传输数据的协议。在IP网络传输一个数字,必须用某种方法来解决不同字节序CPU之间解析数字问题,否则在传输时会发混乱.TCP/IP采用一个简单办法,就是统一规定,在IP网络传输数字必须要用指定的数字字节序传输,这个字节序称为网络序(network order).TCP/IP规定网络序采用大端字节序, 相对的, CPU本身数字表示顺序称为本机序(host order)
.
  因此在网络编程时,在发送数字之前,比如端口号之类,必须把这些数字本机序转换为网络序.在接收后,也需要把网络序数字转为本机序,这样才能让接收的CPU正常使用数字.

  转换的操作是SOCKET定义的标准操作,在支持socket各个操作系统都会要实现如下四个转换函数或宏.

  • unit16_t htons(uint16_t host);
    • htons -->host order to network order by unsigned short
    • 把unsigned short由本机序转网络序
  • unit32_t htonl(uint32_t host);
    • htons -->host order to network order by unsigned long
    • 把long由本机序转网络序,unsigned int也用这个
  • unit16_t ntohs(uint16_t net);
    • ntohs --> network order to host order by unsigned short
    • 把short由网络序转本机序
  • unit32_t ntohl(uint32_t net);
    • ntohs --> network order to host order by unsigned short
    • 把unsigned long由网络序转本机序,unsigned int也用这个

 比较明显,如果本机序是big-endian,则上述操作什么都不做,而little-endian则用到移位来操作,比如Linux这个定义在 netinet/in.h 之中,

#define ntohs (x) {
            __u16 __x = (x);
            ((__u16)( (((__u16)(__x) & (__u16)0x00ffU) << 8) |
                        (((__u16)(__x) & (__u16)0xff00U) >> 8) ));
}

#define ntohl(x) {
            __u32 __x = (x);
            ((__u32)( (((__u32)(__x) & (__u32)0x000000ffUL) << 24) |
                        (((__u32)(__x) & (__u32)0x0000ff00UL) << 8) |
                        (((__u32)(__x) & (__u32)0x00ff0000UL) >> 8) |
                        (((__u32)(__x) & (__u32)0xff000000UL) >> 24) ));
}

 

思考:1.这里为什么要强调是unsigned?

2.上述宏 __u32 __x = (x);起到什么作用。

原文:http://blog.chinaunix.net/u3/105675/showart_2201462.html

(Andrew Huang)
本站文章除注明转载外,均为本站原创或编译欢迎任何形式的转载,但请务必注明出处,尊重他人劳动,同学习共成长。转载请注明:文章转载自:罗索实验室 [http://www.rosoo.net/a/201011/10409.html]
本文出处:blog.chinaunix.net 作者:Andrew Huang
顶一下
(1)
100%
踩一下
(0)
0%
------分隔线----------------------------
发表评论
请自觉遵守互联网相关的政策法规,严禁发布色情、暴力、反动的言论。
评价:
表情:
用户名: 验证码:点击我更换图片
栏目列表
将本文分享到微信
织梦二维码生成器
推荐内容