字节序问题

来源:互联网 发布:ubuntu自带gcc吗 编辑:程序博客网 时间:2024/05/20 20:21

区分:高低地址,高低字节

一、字节序定义

字节序,顾名思义字节的顺序,就是大于一个字节类型的数据在内存中的存放顺序(一个字节的数据当然就无需谈顺序的问题了)。其实大部分人在实际的开发中都很少会直接和字节序打交道。唯有在跨平台以及网络程序中字节序才是一个应该被考虑的问题。

Big-Endian和Little-Endian。引用标准的Big-Endian和Little-Endian的定义如下:
a) Little-Endian就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。
b) Big-Endian就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。
c) 网络字节序:4个字节的32 bit值以下面的次序传输:首先是0~7bit,其次8~15bit,然后16~23bit,最后是24~31bit。这种传输次序称作大端字节序。由于 TCP/IP首部中所有的二进制整数在网络中传输时都要求以这种次序,因此它又称作网络字节序。比如,以太网头部中2字节的“以太网帧类型”,表示后面数据的类型。对于ARP请求或应答的以太网帧类型来说,在网络传输时,发送的顺序是0x08,0x06。

相同字节序的平台在进行网络通信时可以不进行字节序转换,但是跨平台进行网络数据通信时必须进行字节序转换。

UDP/TCP/IP协议规定:把接收到的第一个字节当作高位字节看待,所以如果发送端主机是小端,应该把多字节数据转换成网路字节序在内存中存放,这样在发送端发送数据时,发送的第一个字节是该数值在内存中的起始地址处对应的那个转换成网路字节序后对应的高位字节。
而接收的时候接收端会把接收到的第一个字节放到接收端主机的低地址,而第一个发送过来的是高位字节,所以如果接收端主机是小端模式,应该进行网络字节序到小端的转换。
而平台相同的话则不用转换,低位字节当作高位字节被发送,结果又被放到了接收端低地址处。

栈底 (高地址)
---------------
0x06 -- 低位 
0x08 -- 高位
---------------
栈顶 (低地址)
该字段的值为0x0806。按照大端方式存放在内存中。

网络通讯中,定义网络协议时,都指定用大端模式。所以通用的办法就是,不管主机的字节序是什么,往网络上发送前,都转换成网络字节序,也就是用htons或htonl;而从网络收到的数据,不管主机是什么字节序,都转换成主机字节序,也就是ntohs或者ntohl。

二、高/低地址与高低字节

首先我们要知道我们C程序映像中内存的空间布局情况:在《C专家编程》中或者《Unix环境高级编程》中有关于内存空间布局情况的说明,

----------------------- 最高内存地址 0xffffffff
 | 栈底
 .
              栈
 .
  栈顶
-----------------------
 |
 |
\|/

NULL (空洞)

/|\
 |
 |
-----------------------
                堆
-----------------------
未初始化的数据
----------------(统称数据段)
初始化的数据
-----------------------
正文段(代码段)
----------------------- 最低内存地址 0x00000000
----------
buf[3]
buf[2]
buf[1]
buf[0]
----------
栈顶 (低地址)

现在我们弄清了高低地址,接着来弄清高/低字节,如果我们有一个32位无符号整型0x12345678(呵呵,恰好是把上面的那4个字节buf看成 一个整型),那么高位是什么,低位又是什么呢?其实很简单。在十进制中我们都说靠左边的是高位,靠右边的是低位,在其他进制也是如此。就拿 0x12345678来说,从高位到低位的字节依次是0x12、0x34、0x56和0x78。

高低地址和高低字节都弄清了。我们再来回顾一下Big-Endian和Little-Endian的定义:
以unsigned int value = 0x12345678为例,分别看看在两种字节序下其存储情况,我们可以用unsigned char buf[4]来表示value:
Big-Endian: 低地址存放高位
栈底 (高地址)
---------------
buf[3] (0x78) -- 低位
buf[2] (0x56)
buf[1] (0x34)
buf[0] (0x12) -- 高位
---------------
栈顶 (低地址)

Little-Endian: 低地址存放低位
栈底 (高地址)
---------------
buf[3] (0x12) -- 高位
buf[2] (0x34)
buf[1] (0x56)
buf[0] (0x78) -- 低位
---------------
栈顶 (低地址)

在现有的平台上Intel的X86采用的是Little-Endian,而像Sun的SPARC采用的就是Big-Endian。

三、例子

嵌入式系统开发者应该对Little-endian和Big-endian模式非常了解。采用Little-endian模式的CPU对操作数的存放方式是从低字节到高字节,而Big-endian模式对操作数的存放方式是从高字节到低字节。

例如,16bit宽的数0x1234在Little-endian模式CPU内存中的存放方式(假设从地址0x4000开始存放)为:

内存地址  存放内容
 0x4001    0x12
 0x4000    0x34

而在Big-endian模式CPU内存中的存放方式则为:

内存地址  存放内容
 0x4001    0x34
 0x4000    0x12
 
32bit宽的数0x12345678在Little-endian模式CPU内存中的存放方式(假设从地址0x4000开始存放)为:

内存地址  存放内容
 0x4003     0x12
 0x4002     0x34
 0x4001     0x56
 0x4000     0x78
 
而在Big-endian模式CPU内存中的存放方式则为:

内存地址  存放内容
 0x4003     0x78
 0x4002     0x56
 0x4001     0x34
 0x4000     0x12




例如一个4字节的值为0x1234567的整数与高低字节对应关系:

01
23
45
67
Byte3
Byte2
Byte1
Byte0
高位字节--à---------à--------------à低位字节
 
将在内存中按照如下顺序排放:
内存地址序号
字节在内存中的地址
16进制值
0x03
Byte3
01
0x02
Byte2
23
0x01
Byte1
45
0x00
Byte0
67

字串和整数是相反的,是安字串的索引从低到高存储到内存中的;

     char s[4] = “abc”;  
a
b
c
/0
s[0]
s[1]
s[2]
s[3]
 
 
将在内存中按照如下顺序排放:
内存地址序号
16进制值
指针P的位置
0xbffeadf7
/0
p+3
0xbffeadf6
c
p+2
0xbffeadf5
b
p+1
0xbffeadf4
a
p
 
int main(void)
{
     char s[4] = "abc";
     char *p = s; 
     printf("x, x,    x,    x/n", &s[0], &s[1], &s[2], &s[3]);
     printf("x, x,    x,    x/n", p, p+1, p+2, p+3);
     printf("%c,   %c, %c, %c/n", s[0], s[1], s[2], s[3]);
        
     return 0;
}
输出结果:
[netcool@HFINMSP2 demo]$ ./demo001
bffeadf4,       bffeadf5,       bffeadf6,       bffeadf7
bffeadf4,       bffeadf5,       bffeadf6,       bffeadf7
char names[3][6] = {"Mary", "John", "Alice"};
  假定每个字符占一个内存字节,存放names数组内存的起始地址为5000,则names在内存中的状态如下图:字节序表格表6-4 500050015002500350045005'M''a''r''y''\0' 500650075008500950105011'J''o''h''n''\0' 501250135014501550165017'A''l''i''c''e''\0字节序  注意:内存地址5005和5011的内容是空的,这是因为name[]的第一个元素和第二个元素都只有4个字符长,因此只占用5个连续的内存地址空间。也要注意:每个字符串以一个空字符('\0')结束。
0 0
原创粉丝点击