大小端(网络字节序)等概念

1.大小端定义

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

内存地址0x40000x4001
存储内容(大端)0x000x01
存储内容(小端)0x010x00

大端模式,是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中,这样的存储模式有点儿类似于把数据当作字符串顺序处理:地址由小向大增加,而数据从高位往低位放;这和我们的阅读习惯一致。

小端模式,是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中,这种存储模式将地址的高低和数据位权有效地结合起来,高地址部分权值高,低地址部分权值低。

对于0x11223344 储存如下:

2.为什么会有大小端之分

这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为8bit。但是在C语言中除了8bit的char之外,还有16bit的short型,32bit的long型(要看具体的编译器),另外,对于位数大于8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如果将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。

例如一个16bit的short型x,在内存中的地址为0x0010,x的值为0x1122,那么0x11为高字节,0x22为低字节。对于大端模式,就将0x11放在低地址中,即0x0010中,0x22放在高地址中,即0x0011中。小端模式,刚好相反。

我们常用的X86结构是小端模式,而KEIL C51则为大端模式。很多的ARM,DSP都为小端模式。有些ARM处理器还可以由硬件来选择是大端模式还是小端模式。

3.数据存储

首先,我们看一下数据在内存是怎么存储的

从上边我们可以看出a、b在计算机中存储的是补码,但是顺序却倒着的,这实际上就是小端存储。

程序中一个变量的地址是指这个变量的起始地址,也就是低地址。例如 4字节 int 型变量的地址就是变量的起始地址,即这4字节变量对应的地址空间中的最低地址。

网络字节序

程序中一个变量的地址是指这个变量的起始地址,也就是低地址。例如 4字节 int 型变量的地址就是变量的起始地址,即这4字节地址空间中的最低地址。
而网络发送时,是从起始地址开始操作处理变量数据的,因此会先传输变量的低地址数据,后传输高地址数据。

网络上传输的数据都是字节流,对于一个多字节数值,在进行网络传输的时候,先传递哪个字节?也就是说,当接收端收到第一个字节的时候,它将这个字节作为高位字节还是低位字节处理,是一个比较有意义的问题;
  UDP/TCP/IP协议规定:把接收到的第一个字节当作高位字节看待,这就要求发送端发送的第一个字节是高位字节;而在发送端发送数据时,发送的第一个字节是该数值在内存中的起始地址处对应的那个字节,也就是说,该数值在内存中的起始地址处对应的那个字节就是要发送的第一个高位字节(即:高位字节存放在低地址处);由此可见,多字节数值在发送之前,在内存中因该是以大端法存放的;
  所以说,网络字节序是大端字节序;
  在实际中,当在两个存储方式不同的主机上传输时,需要借助字节序转换函数。

1、什么是大小端模式?

大端模式:是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中。

小端模式:是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中。

举个栗子:

比如数字 0x12 34 56 78在内存中的表示形式为:

  • 大端模式
    低地址 -----------------> 高地址

0x12 | 0x34 | 0x56 | 0x78

  • 小端模式
    低地址 ------------------> 高地址

0x78 | 0x56 | 0x34 | 0x12

可见,大端模式和字符串的存储模式类似。

2、知道大小端有何用?

首先要介绍一下字节序列,所谓的字节序列就是大于或者等于两个字节类型的数据存放在内存中的顺序(一个字节就不必谈序列!)。那么什么时候要用到我们去判断机器是大端模式还是小端模式呢?当我们在跨平台开发或者网络编程的时候就要去关心字节序列了,比如说我们用机器A和B通信,如果A和B的端序都是一样的话,中间自然就不用转换来转换去了。但是如果A和B机器的端序不一样,如果我们不去做相应的转换,我们传过去的二进制序列就是反的!

3、大端小端没有谁优谁劣,各自优势便是对方劣势:

小端模式 :强制转换数据不需要调整字节内容 。

大端模式 :符号位的判定固定为第一个字节,容易判断正负。

4、为什么会有大小端模式之分呢?

这是因为在计算机中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为 8 bit。但是在C 语言中除了 8 bit 的char之外,还有 16 bit 的 short型,32bit的long型(要看具体的编译器),另外,对于位数大于8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如果将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。例如一个16bit的short型 x ,在内存中的地址为 0x0010,x 的值为0x1122,那么0x11位高字节,0x22位低字节。对于大端模式,就将0x11放在低地址中,即0x0010中,0x22放在高地址中,即0x0011中。小端模式,刚好相反。我们常用的X86结构是小端模式,而KEIL C51则为大端模式。很多的ARM,DSP都为小端模式。有些ARM处理器还可以由硬件来选择是大端模式还是小端模式。

一般操作系统都是小端,而通讯协议是大端的

5、如何去判断机器是大端模式还是小端模式?

1 ) 代码实现 :编译环境 VC++6.0 结果输出为 小端存储

#include "stdafx.h"
bool IsBigEndian1()  
{  int a = 1;  //00 00 00 01//通过将int强制类型转换成char单字节,通过判断起始存储位置。即等于 取b等于a的低地址部分char b =  *(char *)&a; /* char b = *(char*)&a;这句话到底干了什么事呢?详解:&a是通过'&'取地址符获取到了a的地址或者可以认为是个指向int类型数据的指针(char *)&a 则把&a强制转换成 char *类型的指针,本句重点就是这个时候发生了截断!<因为还未深入学习指针所以此处对为何发生截断不予详细解释,等后续系统学习后再做补充>截断后,指针(char *)&a 内存放的是存放‘1’的低地址,也就是说此处指向的数值 即b仅为原 始数据的一部分。因为已经确定 是低地址,所以可以通过此处地址指向的数值来判断此环境的存储模式是大端还是小端如果b = 0 即 低地址存放高位数据 则为大端模式如果b = 1 即 低地址存放低位数据 则为小端模式*///printf("b=%0x\n",b); 此句如果被注释  将会输出 b=34 以此更直观的看到效果if( b == 0)  {  return true;  }  return false;  
}
int main(int argc, char* argv[])
{if(IsBigEndian1()==true){printf("大端存储\n");}else{printf("小端存储\n");}return 0;
}

2)联合体union的存放顺序是所有成员都从低地址开始存放,利用该特性可以轻松地获得了CPU对内存采用Little-endian还是 Big- endian模式读写:

  • 算法思路:定义一个联合体,根据联合体成员起始地址相同巧妙的访问到了第一个字节地址的数据。
  • 联合体类型特点:
#include "stdafx.h"
/*
联合体union的存放顺序是所有成员都从低地址开始存放,利用该特性
可以轻松地获得了CPU对内存采用Little-endian还是Big-endian模式读写:
*/
bool IsBigEndian2()  
{  union NUM  {  int a;  char b;  }num;  num.a = 0x1234;  if( num.b == 0x12 )  {  return true;  }  return false;  
}
int main(int argc, char* argv[])
{if(IsBigEndian2()==true){printf("Big-endian\n");}else{printf("Little-endian\n");}return 0;
}

6.常见字节序:

常见CPU的字节序

Big Endian : PowerPC、IBM、Sun
Little Endian : x86、DEC
ARM既可以工作在大端模式,也可以工作在小端模式。

常见文件的字节序

Adobe PS – Big Endian
BMP – Little Endian
DXF(AutoCAD) – Variable
GIF – Little Endian
JPEG – Big Endian
MacPaint – Big Endian
RTF – Little Endian

另外,在C语言中,默认是小端(但在一些对于单片机的实现中却是基于大端,比如Keil 51C),Java是平台无关的,默认是大端。在网络上传输数据普遍采用的都是大端。

7.多角度理解

1.从软件的角度上,不同端模式的处理器进行数据传递时必须要考虑端模式的不同。如进行网络数据传递时,必须要考虑端模式的转换。 在对普通文件进行处理也需要考虑端模式问题。在大端模式的处理器下对文件的32,16位读写操作所得到的结果与小端模式的处理器不同。单纯从软件的角度理解上远远不能真正理解大小端模式的区别。事实上,真正的理解大小端模式的区别,必须要从系统的角度,从指令集,寄存器和数据总线上深入理解,大小端模式的区别。

2.从系统的角度上,处理器在硬件上由于端模式问题在设计中有所不同。从系统的角度上看,端模式问题对软件和硬件的设计带来了不同的影响,当一个处理器系统中大小端模式同时存在时,必须要对这些不同端模式的访问进行特殊的处理。如果从实际应用的角度说,采用小端模式的处理器需要在软件中处理端模式的转换,因为采用小端模式的处理器在与小端外设互连时,不需要任何转换。而采用大端模式的处理器需要在硬件设计时处理端模式的转换。大端模式处理器需要在寄存器,指令集,数据总线及数据总线与小端外设的连接等等多个方面进行处理,以解决与小端外设连接时的端模式转换问题。在寄存器和数据总线的位序定义上,基于大小端模式的处理器有所不同。

3.实际例子

虽然很多时候,字节序的工作已由编译器完成了,但是在一些小的细节上,仍然需要去仔细揣摩考虑,尤其是在以太网通讯、MODBUS通讯、软件移植性方面。这里,举一个MODBUS通讯的例子。在MODBUS中,数据需要组织成数据报文,该报文中的数据都是大端模式,即低地址存高位,高地址存低位。假设有一16位缓冲区m_RegMW[256],因为是在x86平台上,所以内存中的数据为小端模式:m_RegMW[0].low、m_RegMW[0].high、m_RegMW[1].low、m_RegMW[1].high……
为了方便讨论,假设m_RegMW[0] = 0x3456; 在内存中为0x56、0x34。

现要将该数据发出,如果不进行数据转换直接发送,此时发送的数据为0x56,0x34。而Modbus是大端的,会将该数据解释为0x5634而非原数据0x3456,此时就会发生灾难性的错误。所以,在此之前,需要将小端数据转换成大端的,即进行高字节和低字节的交换,此时可以调用步骤五中的函数BigtoLittle16(m_RegMW[0]),之后再进行发送才可以得到正确的数据。

本文链接:https://my.lmcjl.com/post/10375.html

展开阅读全文

4 评论

留下您的评论.