您的位置:首页 > 其它

实测数据在内存中的存放:大小端模式

2013-02-02 16:26 246 查看
<<C语言深度剖析>>学习中....

最近在学习C语言深度剖析,读到了关于数据在内存中的存放:大端、小端模式...

关于 big-endian and little-endian ...

自编Test小程序:

#include <stdio.h>

#include <stdlib.h>

int main(void)

{

int a[5] = {0x1111,0x23456789,0x3333,0x4444,0x5555};

int *p1 = (int *)((int)a);

int *p2 = (int *)((int)a+1);

int *p3 = (int *)((int)a+2);

int *p4 = (int *)((int)a+3);

printf("%8x %8x %8x %8x\n",a,(a+1),(a+2),(a+3));

printf("%8x %8x %8x %8x\n",*a,*(a+1),*(a+2),*(a+3));

printf("%8x %8x %8x %8x\n",p1,p2,p3,p4);

printf("%8x %8x %8x %8x\n",*p1,*p2,*p3,*p4);

system("pause");

return 0;

}

运行结果: (Dev- C++编译)





查看内存和数据:









总结:

1、X86系统中,一个字节占8位(废话)。

2、小端模式中,低位数据存储于低地址中,高位数据存储在高地址中。

3、0x12ff6c地址内存储的数据是:0x00001111 , 0x12ff70地址内存储的数据是:0x234567890

4、0x23存储于高地址(0x12ff73)中 ,0x89存储于高地址(0x12ff70)中。

5、 取出 0x12ff6c 中的数据,则为:0x 00 00 11 11

取出 [b]0x12ff6d 中的数据,则为:0x 89 00 00 11 [/b]

[b]取出 0x12ff6e 中的数据, 则为:0x 67 89 00 00 [/b]

[b]取出 0x12ff6f 中的数据, 则为:0x 45 67 89 00 [/b]

[b]取出 0x12ff70 中的数据, 则为:0x 23
45 67 89
[/b]

int 型数据占据4个字节,每4字节为一个数据块,读数据是存数据的逆方向,

即高地址的数据放在高位, 低地址的数据放在低位。

知识扩展:

(from:ifreecoding的博客)

2.4.4
字节大小端

大端字节序对应的英文单词是big-endian,小端字节序对应的英文单词是little-endian。

endian这个单词在英文词典里一般查不到,这个单词出自Jonathan Swift写的讽刺小说《格列佛游记》,这本书中描述了一个小人国里总会发生一些意想不到的事情,有一次小人们因为是应该先从鸡蛋大的一端敲开还是小的一端敲开而引发了战争,支持从大端敲开的人被归为Big
Endian,支持从小端敲开的人被归为Littile Endian。

在计算机世界里同样存在着这种“战争”,各个芯片厂商为了自身利益的需要提出了不同的两种字节序,从而引发了应该使用哪种字节序的争论。为了平息这场争论,Danny Cohen在其著名的论文"On Holy Wars and a
Plea for Peace"中引入了big-endian和little-endian,分别对应着这两种字节序。这两种字节序共存的现实给软硬件设计带来了不小的麻烦,这种非常形象贴切的描述对这种情况真是一个不小的讽刺。

绝大部分处理器的最小存储单元是Byte,也就是说每个地址对应一个字节的数据,8位机时代对于处理器来说是不存在大小端问题的,它的机器指令只能以Byte为单位对存储数据进行访问,对多Bytes长度的数据类型进行访问也是由相对应多个的机器指令组成的,每条机器指令中规中矩的访问一个Byte的数据。但到16位机、32位机甚至64位机时代问题就来了,为了保持兼容性,这些处理器仍然是以Byte作为最小的存储单元,每个地址仍对应一个Byte数据,但为了提高性能,处理器增加了同时访问多字节的机器指令,以32位机为例,它的一条机器指令可以同时访问32bits的数据,也就是4地址的数据,对应4个字节。4个字节的数据作为一个整体表示一个数,那么它们在内存中是如何存放呢,这就涉及到大小端模式的问题了,看下图:



图 24 数值在内存中的存放模式

大端模式是将数的高位字节放在低位地址,将数的低位字节放在高位地址,而小端模式则正好反过来,是将数的高位字节放在高位地址,将数的低位字节放在低位地址,如上图所示。

我们再来看看上节例2.4.7中值为0x12345678的int型变量addr在Memory窗口的截图,如下图所示:



图 25 查看Memory内存窗口

其中最高位字节0x12被放在了最高地址0x2000020B,次高位字节0x34被放在了地址0x2000020A,次低位字节0x56被放在了地址0x20000209,最低位字节0x78被放在了地址0x20000208,按照这个存放顺序来看,我们所使用的处理器是小端模式的。

如果我们只使用变量而不直接访问内存是不会觉察到大小端模式存在的,比如说我们定义了一个int型的变量addr,在使用addr这个变量时它所占用的4字节内存空间是作为一个整体出现的,我们所能看到的只有变量addr,看不到它内部的4个字节,变量addr像一个黑盒一样罩住了它内部的4个字节的内存空间,我们访问addr时,处理器会按照大端或者小端的规则将这4个字节内存空间中的数据与变量addr的数值对应起来。对内存的访问分为读和写两种操作,下面我们仍以0x20000208~0x2000020B地址的变量addr为例来看看大小端模式的工作流程。

大端模式

将数0x12345678写入到变量addr中时,硬件就会将数0x12345678看作成4个字节0x12、0x34、0x56和0x78,分别存入0x20000208~0x2000020B这4个地址中,每个字节的数与地址按照大端模式的规则一一对应,这种对应关系是由硬件自动完成的。



图 26 大端模式

当读取变量addr数值时,硬件就会从0x20000208~0x2000020B这4个地址中读出4个字节,按照大端模式的对应规则将这4个地址中的4个字节恢复成0x12345678,这种对应规则也是由硬件自动完成的。

小端模式

从软件角度来看,小端模式的读写过程与大端模式的读写过程是一样的,写过程是将数0x12345678写入到变量addr中,读过程是从addr中取得数0x12345678,与大端模式不同之处在于硬件部分,硬件在执行读写时采用了数与地址不同的映射关系,这才是产生大小端模式的根本原因。



图 27 小端模式

硬件通过映射关系屏蔽了变量addr所对应的内存空间细节,因此软件访问变量addr根本就感觉不到大小端的存在,这就好比是我们把钱存到银行,我们不知道银行把这些钱放在哪里了,我们只需要知道我们还能从银行取出这些钱就行了。如果我们进入银行跟踪这些钱,也许会发现这些钱根本就没在银行里,可能已经被其他人取走了,在我们取钱时银行又拿了别人的钱给了我。进入银行就好比我们绕过了硬件的映射关系,直接去查看内存,大小端的问题就呈现在我们眼前了。

在上一节我们说过Memory窗口有多种查看方式,我们将查看方式更改为int类型,图25就会以int的类型显示数据,如下所示:



图 28 查看Memory内存窗口

可以看到变量addr在内存中的4个地址组合成一个整体显示出了addr的数值,与变量的值是一样的都是12345678,这样就方便我们查看内存数据了,不需要再考虑大小模式转换。不过这种方式是将内存中所有的数据都按照设置的类型显示,比如说当前显示类型为int型,那么几个char型、short型数也会被组合成int型来显示。

长度超过1个Byte的类型都存在大小端的问题,比如说2Bytes的short型、unsigned short型也需要按照大小端的规则在内存中存放。

有的处理器采用了小端模式,如PC机里的Intel X86 CPU,ARM等处理器,有的处理器则采用了大端模式,如PowerPC等处理器,还有一些处理器同时支持这2种模式,如TI的一些DSP处理器,可以选择使用其中的一种模式。

对于8bits的51单片机来说硬件每次只能读写一个Byte的数据,不存在大小端模式的概念,但它的C语言却是能支持16bits的int型变量,这就需要使用2个地址存放数据,这也会涉及到变量的存放规则,比如说它的一个16bits的int型变量值为0x1234,地址是0x40和0x41,其中0x12被存储在0x40地址,0x34被存储在0x41地址,从这点来看好像是大端模式,但这并不是我们前面介绍过的大端模式。原因在于51单片机对16bits数据读写时是使用了2条机器指令,每条机器指令会有一个地址与之对应,可以认为是重复了2次对8bits数据的操作组成了一次对16bits数据的操作,对于硬件来说,看不到16bits的数据,只能看到8bits的数据,数的高低位与地址的对应关系是由编译器决定的,而不是由硬件决定的。而对于本手册所使用的ARM处理器来说,对16bits数据读写时只使用1条机器指令,这条指令只对应一个地址,而一个地址只对应8bits数据,因此这就需要使用1个地址存储2个地址的数据,这就涉及到了大小端模式问题,数的高低位与地址的对应关系是由硬件决定的,编译器根本插不上手。

因此说51单片机是不存在大小端模式的,它所表现出的大小端是编译器的大小端,是编译器安排了数的存储模式,与处理器无关。

除了在数据存储时会体现大小端的概念,在数据传输时也会存在大小端的问题,数据传输是从一个处理器发送到另一个处理器的过程,尽管物理层中的数据不全是以Byte为单位进行传输的,但数据在收发两端处理器中仍然是以Byte为单位进行管理的,这就会涉及到大小端问题,比如说处理器A中有一个int型的变量addr,它的值为0x12345678,需要将这个值发送给处理器B,如果按照从数值高位到低位的顺序发送,那么发送出的数据为0x12->0x34->0x56->0x78,接收端处理器B也按同样的顺序接收到这4个Bytes的数据,但问题也来了,处理器B不知道处理器A发送的顺序,如果按照从高位到低位的顺序组合,那么接收到的数据就是0x12345678,如果按照从低位到高位的顺序组合,那么接收到的数据就是0x78563412,这就出错了,因此在网络传输中需要收发双方约定好发送的顺序。

数据发送时一般会按照内存地址增长的顺序发送数据,接收数据时也会按照内存地址增长的顺序存放数据,这称之为网络字节序,对于按大端模式存放的数来说,正好是从数的高位向低位的发送过程,因此网络字节序也称为大端字节序。比如说发送端处理器A是大端模式的,它有一个int型的变量a,它的数值为0x12345678,地址为0x20000208~0x2000020B,那么数值0x12就会存放在地址0x20000208中,数值0x34就会存放在地址0x20000209中,数值0x56就会存放在地址0x2000020A中,数值0x78就会存放在地址0x2000020B中,按照网络字节序的顺序发送,发送端就会按照0x12->0x34->0x56->0x78的顺序发送数据,与数值0x12345678的写法是一致的。如果接收端处理器B将这4个Bytes的数据按照网络字节序存入0x10000000~0x10000003地址中,那么数值0x12就会存放在地址0x10000000中,数值0x34就会存放在地址0x10000001中,数值0x56就会存放在地址0x10000002中,数值0x78就会存放在地址0x10000003中,如果这4个地址是int型变量b的内存空间,那么对于大端模式的处理器B来说变量b的值就是0x12345678,这样就实现了数值传输。当然,如果收发双方都约定使用小端模式来收发数据也是可以的,只要收发端使用相同的字节序就可以正确收发数据。

收发端使用相同的字节序,但收发端处理器的端模式不同也会有问题,来看下表,收发双方采用不同的大小端通过网络字节序发送int型数据:



表 7 网络传输中收发端字节序对数据传输的影响

可以看到,尽管采用了相同的传输字节序,但收发两端处理器的字节序不同也会导致收发的数据不一致,解决这个问题的方法是发送端在发送前需要将发送的数据从处理器所使用的端模式转换成传输字节序,接收端在接收后需要将接收的数据从传输字节序转换成处理器所使用的端模式,来看下表:



表 8 网络传输中收发端字节序的修改

在linux中使用了htonl、ntohl、htons、ntohs这4个宏(在第6章讲宏)来完成网络字节序的转换,其中的h代表host,也就是处理器的端模式,n代表net,是网络字节序,l代表32bits数据,s代表16bits数据,那么htonl就是将32bits数据从处理器的端模式转换成网络字节序,ntohl是将32bits数据从网络字节序转换成处理器的端模式,htons是将16bits数据从处理器的端模式转换成网络字节序,ntohs是将16bits数据从网络字节序转换成处理器的端模式。

在设计芯片管脚的硬件连接中也会存在大小端的问题,比如说在两块芯片间使用32bits并行数据线传输数据,两块芯片对高低数据线的定义上也会出现大小端模式的问题,这里就不详细阐述了。

上述三种会产生大小端问题的场景都有一个共性,那就是破坏了数据的黑盒结构,不是由同一个执行单元对黑盒进行操作,而是由多个执行单元对黑盒进行操作,多个执行单元对黑盒内的理解不一致就会产生大小端问题。

学习自:

<<C语言深度剖析>>

http://bbs.chinaunix.net/thread-3753950-1-1.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: