字节存储排序:大端和小端的判别及转换

当前的存储器,多以byte为访问的最小单元,当一个逻辑上的地址必须分割为物理上的若干单元时就存在了先放谁后放谁的问题,于是端(endian)的问题应运而生了,对于不同的存储方法,就有大端(big-endian)和小端(little- endian)两个描述。
其实大端也叫高尾端,小端也叫低尾端;这样记的话比较容易理解;
字节排序按分为大端和小端,概念如下
大端(big endian):低地址存放高有效字节
小端(little endian):低字节存放地有效字节

现在主流的CPU,intel系列的是采用的little endian的格式存放数据,而motorola系列的CPU采用的是big endian,ARM则同时支持 big和little,网络编程中,TCP/IP统一采用大端方式传送数据,所以有时我们也会把大端方式称之为网络字节序。

特别需要注意的是,C/C++语言编写的程序里数据存储顺序是跟编译平台所在的CPU相关的,而 JAVA编写的程序则唯一采用big endian方式来存储数据。这里我就只讨论C/C++语言的情况。

1.大端和小端的方式及判断
举个例子说明,我的机子是32位windows的系统,处理器是AMD的。对于一个int型数0x12345678,为方便说明,这里采用16进制表示。这个数在不同字节顺序存储的CPU中储存顺序如下:


0x12345678   16进制,两个数就是一字节


高有效字节——>低有效字节: 12 34 56 78


          低地址位     高低址位


大端:  12  34        56   78


小端: 78  56        34   12


例子1:在内存中双字0x01020304(DWORD)的存储方式
内存地址
4000&4001&4002&4003
LE 04 03 02 01
BE 01 02 03 04
例子2:如果我们将0x1234abcd写入到以0x0000开始的内存中,则结果为
             big-endian   little-endian
0x0000 0x12                 0xcd
0x0001 0x34                 0xab
0x0002 0xab                 0x34
0x0003 0xcd                 0x12
x86系列CPU都是little-endian的字节序.


下面验证下本机CPU属于哪种字节存储顺序。代码如下:

#include <iostream> 
 
using namespace std;
 
typedef unsigned int UINT;
typedef unsigned char UCHAR;
 
int main()
{
    UINT i=0x12345678;
    cout<<hex<<i<<endl;
    UCHAR *p = (UCHAR*)&i;          //将i的地址传给数组指针p,实际上p指向的地址是i在内存中存储的第一个字节,大端就是0x12,小端就是0x78
    if((*p==0x78)&(*(p+1)==0x56))        
        cout<<"小端"<<endl;
    else if((*p==0x12)&(*(p+1)==0x34))
        cout<<"大端"<<endl;
    else
        cout<<"这是神马字节顺序呢?";
    return 0;
}
调试显示时小端,我用的机子字节存储为小端方式。

或者如下代码:

#include <stdlib.h>
#include <stdio.h>
int main(int argc, char **argv)
{
    union {
        short s;
        char c[sizeof(short)];
    } un;
    un.s = 0x0102;
    if(sizeof(short)==2) {
        if(un.c[0]==1 && un.c[1] == 2)
            printf("big-endian\n");
        else if (un.c[0] == 2 && un.c[1] == 1)
            printf("little-endian\n");
        else
            printf("unknown\n");
    } else
        printf("sizeof(short)= %d\n",sizeof(short));
    exit(0);
}


2.大端和小端的字节转换


当两台采用不同字节序的主机通信时,在发送数据之前都必须经过字节序的转换成为网络字节序(即大端方式)后再进行传输。此外用C/C++在小端方式的机器上编写的程序与java程序互通时也要进行大端和小端的转换。


这里所谓转换就是改变字节的排序,使交互时数据保持一致。举一个例子,还是16进制表示的数0x12345678,在小端机器上排序为0x78563412,当内存中这样的数传输时,在大端方式下就是0x78563412这个值,与原值不同,要想与原值相同,在传输前,在大端方式下就该是0x12345678,这时原数在内存中为0x12345678,即将原数据0x12345678在内存存储序列为0x12345678,也就是要转换成大端方式。


要传输值:12 34 56 78


不转换时,小端:78 56 34 12


转换为大端:12 34 56 78


根据上面的大端和小端字节排序,可以方便的用移位运算完成转换功能。从小端转到大端代码如下:

#include <iostream>
 
using namespace std;
 
typedef unsigned int UINT;
typedef unsigned char UCHAR;
 
int main()
{
    UINT i=0x12345678;
    cout<<hex<<i<<endl;
    UCHAR *p = (UCHAR*)&i; 
    UINT num,num1,num2,num3,num4;
    num1=(UINT)(*p)<<24;
    num2=((UINT)*(p+1))<<16;
    num3=((UINT)*(p+2))<<8;
    num4=((UINT)*(p+3));
    num=num1+num2+num3+num4;
 
    cout<<"num1:"<<hex<<num1<<endl;     //看num1的16进制表示,下同
    cout<<"num2:"<<hex<<num2<<endl;
    cout<<"num3:"<<hex<<num3<<endl;
    cout<<"num4:"<<hex<<num4<<endl;
    cout<<"num:"<<hex<<num<<endl;
 
    unsigned char *q = (unsigned char*)&num;
    if((*q==0x78)&(*(q+1)==0x56))          
        cout<<"小端"<<endl;
    else if((*q==0x12)&(*(q+1)==0x34))
        cout<<"大端"<<endl;
    else
        cout<<"这是神马字节顺序呢?";
    return 0;
}
至于说(UINT)(*p)为什么要移24位,其实是很好理解的,将0x00000012变成0x12000000,不就是向左移24位吗。

当然,向上面这样写时为了方便理解,可以更简单的写一个函数用于完成上面的转换功能,函数如下:

UINT EndianConvertLToB(UINT InputNum) {
    UCHAR *p = (UCHAR*)&InputNum;
    return(((UINT)*p<<24)+((UINT)*(p+1)<<16)+
               ((UINT)*(p+2)<<8)+(UINT)*(p+3));
}
同样的原理适用于大端转小端,但是大端转小端时移位有差别,函数如下:

UINT EndianConvertBToL(UINT InputNum) {
    UCHAR *p = (UCHAR*)&InputNum;
    return(((UINT)*p)+((UINT)*(p+1)<<8)+
               ((UINT)*(p+2)<<16)+(UINT)*(p+3)<<24);
}
阅读更多
版权声明:【博主微信公众号:不忘初心的行者】【本文为博主原创,未经博主允许不得转载】 https://blog.csdn.net/u010193457/article/details/52541923
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭