功能描述:大端存储和小端存储的含义及其使用方法
一、大小端存储的含义
“大端”和“小端”表示多字节值的哪一端存储在该值的起始地址处;小端存储在起始地址处,即是小端字节序;大端存储在起始地址处,即是大端字节序。
大端存储模式:数据的低位保存在内存中的高地址中,数据的高位保存在内存中的低地址中
小端存储模式:数据的低位保存在内存中的低地址中,数据的高位保存在内存中的高地址中
计算机系统大多数采用小端存储模式。
二、为什么会有大小端存储模式
1、CPU 和编译器的不同
在计算机系统中是以字节为单位的,每个地址单元都对应着一个字节,一个字节为 8bit,但是在 C语言中除了 8bit 的 char 之外,还有 16bit 的 short 型,32bit 的 long 型(要看具体的编译器),另外,对于位数大于 8 位的处理器,例如 16 位或者 32 位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题,因此就导致了大端存储模式和小端存储模式。
例如一个 16bit 的 short 型 A,在内存中的地址为 0x0010,A 的值为 0x1122,那么 0x11 为高字节,0x22 为低字节。对于大端模式,就将 0x11 放在低地址中,即地址 0x0010中,0x22 放在高地址中,即地址 0x0011 中。小端模式刚好相反,将 0x22 放在低地址中,即地址 0x0010中,0x11 放在高地址中,即地址 0x0011 中。
常用的x86结构是小端模式,而KEIL C51则为大端模式,很多的ARM,DSP都为小端模式,有些ARM处理器还可以由硬件来选择是大端模式还是小端模式。
2、网络字节序
网络上传输的数据都是字节流,对于一个多字节数值,在进行网络传输的时候,先传递哪个字节,也就是说,当接收端收到第一个字节的时候,它将这个字节作为高位字节还是低位字节处理,是一个比较有意义的问题。
UDP/TCP/IP协议规定:把接收到的第一个字节当作高位字节看待,这就要求发送端发送的第一个字节是高位字节,而在发送端发送数据时,发送的第一个字节是该数值在内存中的起始地址处对应的那个字节,也就是说,该数值在内存中的起始地址处对应的那个字节就是要发送的第一个高位字节(即:高位字节存放在低地址处);由此可见,多字节数值在发送之前,在内存中应该是以大端法存放的,所以说,网络字节序是大端字节序。
比如,我们经过网络发送整型数值 0x12345678 时,在 x86 平台中,它是以小端发存放的,在发送之前需要使用系统提供的字节序转换函数htonl( )将其转换成大端法存放的数值,再进行发送。
三、如何判断 CPU 使用大端存储还是小端存储的
方法1:类型降低
把一个占字节数大的变量赋值给一个占字节数小的变量,小的那个会将大的从起始地地址偏移小的大小个长度的数据拿过来。如果是大端,小的数据拿过来就是高位的数据,如果是小端,小的数据拿过来的就是低位的数据。比如说,short big = 0xff00;char litter = big;大端 litter = 0xff,小端litter = 00。
int main()
{
short big = 0xff00;
char litter = big;
if (litter == 0xff)
{
cout << "大端" << endl;
}
else
{
cout << "小端" << endl;
}
return 0;
}
程序输出为:小端
方法2:使用指针,类型强转
int main()
{
short a = 0xff00;
char* b = (char*)&a;
if (*b == 0xff)
{
cout << "大端" << endl;
}
else
{
cout << "小端" << endl;
}
return 0;
}
程序输出为:小端
方法3:使用union
int main()
{
union un
{
char a;
short b;
};
un u;
u.b = 0xff00;
if (u.a == 0xff)
{
cout << "大端" << endl;
}
else
{
cout << "小端" << endl;
}
return 0;
}
程序输出为:小端
四、大小端存储数据转化
方法1:调用系统自带的大小端转化函数
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
uint32_t ntohl(uint32_t netlong);
uint16_t htons(uint16_t hostshort);
uint16_t ntohs(uint16_t netshort)
各函数介绍如下:
uint32_t htonl(uint32_t hostlong);
htonl 表⽰ host to network long ,⽤于将主机 unsigned int 型数据转换成⽹络字节顺序,即将小端存储转化为大端存储数据;
uint32_t ntohl(uint32_t netlong);
ntohl 表⽰ network to host long,⽤于将网络 unsigned int 型数据转换成主机字节顺序,即将大端存储转化为小端存储数据;
uint16_t htons(uint16_t hostshort);
htons 表⽰ host to network short ,⽤于将主机 unsigned short 型数据转换成⽹络字节顺序,即将小端存储转化为大端存储数据;
uint16_t ntohs(uint16_t netshort)
ntohs 表⽰ network to host short,⽤于将网络 unsigned short 型数据转换成主机字节顺序,即将大端存储转化为小端存储数据。
方法2:调用Qt的大小端转化函数,即交换字节顺序
包含头文件
T qFromBigEndian(T src)
将大端字节顺序的数据 src 转化为本地主机字节顺序的数据。如果本地主机字节顺序为 little-endian(小端存储),例如 x86 的 CPU 架构,将返回交换字节顺序后的小端存储数据;否则将返回未修改的 src。
T qFromBigEndian(const void *src)
从内存位置 src 读取一个 big-endian 数据,转化为本地主机字节顺序的数据。如果本地主机字节顺序为 little-endian(小端存储),例如 x86 的 CPU 架构,将返回交换字节顺序后的小端存储数据;否则将返回未修改的 src。
将小端字节顺序的数据 src 转化为本地主机字节顺序的数据。如果本地主机字节顺序为大端,例如 PowerPC 的 CPU 架构上,将返回交换字节顺序后的小端存储数据;否则将返回未修改的 src。
T qFromLittleEndian(const void *src)
从内存位置 src 读取一个 little-endian 数据,转化为本地主机字节顺序的数据。如果本地主机字节顺序为大端,例如 PowerPC 的 CPU 架构上,将返回交换字节顺序后的小端存储数据;否则将返回未修改的 src。
从主机字节顺序读取 src,并转化为大端字节顺序数据。如果本地主机字节顺序为 little-endian(小端存储),例如 x86 的 CPU 架构,将返回交换字节顺序后的大端存储数据;否则将返回未修改的 src。
void qToBigEndian(T src, void *dest)
从主机字节顺序读取 src,以大端字节顺序将 src 数据写入到 dest 地址处。如果本地主机字节顺序为 little-endian(小端存储),例如 x86 的 CPU 架构,将返回交换字节顺序后的大端存储数据;否则将返回未修改的 src。
从主机字节顺序读取 src,并转化为小端字节顺序数据。如果本地主机字节顺序为大端,例如 PowerPC 的 CPU 架构上,将返回交换字节顺序后的小端存储数据;否则将返回未修改的 src。
void qToLittleEndian(T src, void *dest)
从主机字节顺序读取 src,以小端字节顺序将 src 数据写入到 dest 地址处。如果本地主机字节顺序为大端,例如 PowerPC 的 CPU 架构上,将返回交换字节顺序后的小端存储数据;否则将返回未修改的src。
五、测试验证
#include <QApplication>
#include <QtEndian>
#include <QDebug>
int main(int argc, char *argv[])
{
// short a = 0x0102;
// short b = qFromBigEndian(a);
// qDebug() << QString::number(a,16) << QString::number(b,16);
// short a = 0x0102;
// *a = 0x0102;
// short b = qFromBigEndian((const void *)0x60fdde);
// qDebug()<< &a << QString::number(a,16) << QString::number(b,16);
// short a = 0x0102;
// short b = qFromLittleEndian(a);
// qDebug() << QString::number(a,16) << QString::number(b,16);
// short a = 0x0102;
// short b = qFromLittleEndian((const void *)(&a));
// qDebug() << QString::number(a,16) << QString::number(b,16);
// short a = 0x0102;
// short b = qToBigEndian(a);
// qDebug() << QString::number(a,16) << QString::number(b,16);
// short a = 0x0102;
// short b;
// qToBigEndian(a,&b);
// qDebug() << QString::number(a,16) << QString::number(b,16);
// short a = 0x0102;
// short b = qToLittleEndian(a);
// qDebug() << QString::number(a,16) << QString::number(b,16);
short a = 0x0102;
short b;
qToLittleEndian(a,&b);
qDebug() << QString::number(a,16) << QString::number(b,16);
return 0;
}