大小端判断方法和转换

1.什么是大小端?

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

2.大小端定义

大端模式:即把数据的高字节放到低地址中
小端模式:高字节放到高地址中

3.图解

画张图简单解释下大小端的区别,比如我们要存取一个0x12345678的数据,在大小端机器的存取方式分别是:

大端模式:

低地址 -----------------> 高地址

0x12 | 0x34 | 0x56 | 0x78

小端模式:

低地址 ------------------> 高地址

0x78 | 0x56 | 0x34 | 0x12


4064394-b0db427b8b71a78f.png
大小端

4 判断机器大小端方式

1.字符指针判断
在32位平台下,int占4个字节,而char类型的指针是占一个字节的,如果我们把int强传为char类型的指针,只会保存一个字节的数据,那么我们只需要判断char里面的第一个字节和int里面的第一个字节是否是一致即可判断。
如果一致则为小端模式,反之为大端模式。

下面代码我们令 int a=1 如果是小端模式,int下1会存放在在低地址处,而强传为char类型的指针,1也在低地址处,所以可以判断。

#include <iostream>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <string>
#include <string.h>
#include <memory>
#include <stdint.h>

int main() {
    // judge  large or small

    uint64_t flag = 2;
    uint64_t *raw_ptr = &flag;

    char *p = reinterpret_cast<char*>(raw_ptr);
    
    if(*(p) == 2) {
        std::cout << "small" << std::endl;
    } else {
        std::cout << "large" << std::endl;
    }

    return 0;
}

4064394-96cabd868bf53c56.png
image.png

2.联合体判断
由于联合体所有数据共享一块地址空间,存放数据的所有成员都是从低地址开始存放,所以我们可以在联合体内定义一个int和一个char类型变量,然后在外部实例化的时候创建int变量,用char变量调用,相当于隐式类型转化,如果结果为1,则低字节存放在低地址,既是小端机器,反之大端机器。

void small_large_union() {
    union flag{
        uint64_t flag1;
        char flag2;
    };

    flag f;
    f.flag1 =1;

    if(f.flag2 == 1) {
        std::cout << "small" << std::endl;
    } else {
        std::cout << "large" << std::endl;
    }
    
}

这里复习一下union
union 型数据所占的空间等于其最大的成员所占的空间。对 union 型的成员的存取都是相对于该联合体基地址的偏移量为 0 处开始,也就是联合体的访问不论对哪个变量的存取都是从 union 的首地址位置开始。

联合是一个在同一个存储空间里存储不同类型数据的数据类型。这些存储区的地址都是一样的,联合里不同存储区的内存是重叠的,修改了任何一个其他的会受影响。

共用体表示几个变量共用一个内存位置,在不同的时间保存不同的数据类型和不同长度的变量。在union中,所有的共用体成员共用一个空间,并且同一时间只能储存其中一个成员变量的值。

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

1)常见CPU的字节序

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

5.大小端转换

法1:位运算

#include<stdio.h>  
  
typedef unsigned int uint_32 ;  
typedef unsigned short uint_16 ;  
 
//16位
#define BSWAP_16(x) \
    (uint_16)((((uint_16)(x) & 0x00ff) << 8) | \
              (((uint_16)(x) & 0xff00) >> 8) \
             )
             
//32位               
#define BSWAP_32(x) \
    (uint_32)((((uint_32)(x) & 0xff000000) >> 24) | \
              (((uint_32)(x) & 0x00ff0000) >> 8) | \
              (((uint_32)(x) & 0x0000ff00) << 8) | \
              (((uint_32)(x) & 0x000000ff) << 24) \
             )  
 
//无符号整型16位  
uint_16 bswap_16(uint_16 x)  
{  
    return (((uint_16)(x) & 0x00ff) << 8) | \
           (((uint_16)(x) & 0xff00) >> 8) ;  
}  
 
//无符号整型32位
uint_32 bswap_32(uint_32 x)  
{  
    return (((uint_32)(x) & 0xff000000) >> 24) | \
           (((uint_32)(x) & 0x00ff0000) >> 8) | \
           (((uint_32)(x) & 0x0000ff00) << 8) | \
           (((uint_32)(x) & 0x000000ff) << 24) ;  
}  
 
int main(int argc,char *argv[])  
{  
    printf("------------带参宏-------------\n");  
    printf("%#x\n",BSWAP_16(0x1234)) ;  
    printf("%#x\n",BSWAP_32(0x12345678));  
    printf("------------函数调用-----------\n");  
    printf("%#x\n",bswap_16(0x1234)) ;  
    printf("%#x\n",bswap_32(0x12345678));  
      
    return 0 ;  
}  
输出结果:
------------带参宏-------------
0x3412
0x78563412
------------函数调用-----------
0x3412
0x78563412

法2:arpa.inet.h网络库中有一些将数据转换为网络字节顺序的函数

htonl() //32位无符号整型的主机字节顺序到网络字节顺序的转换(小端->>大端)
htons() //16位无符号短整型的主机字节顺序到网络字节顺序的转换 (小端->>大端)
ntohl() //32位无符号整型的网络字节顺序到主机字节顺序的转换 (大端->>小端)
ntohs() //16位无符号短整型的网络字节顺序到主机字节顺序的转换 (大端->>小端)

注,主机字节顺序,X86一般多为小端(little-endian),网络字节顺序,即大端(big-endian);

举个栗子:

#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <arpa/inet.h>

int main(int argc, char * argv[]) {
    union flag{
        int a;
        char b[2];
    };

    flag f;
    f.b[0] = 0x11;
    f.b[1] = 0x22;
    //0x2211 为小端  0x1122 为大端
    

    //htons
    printf("original: 0x%x\n",f.a);
    printf("trans:    0x%x\n",htons(f.a));

    return 0;
}
4064394-9473ae16151d710b.png
结果

6.大小端问题的来源

先补充两个关键词,MSB和LSB:
MSB:MoST Significant Bit ------- 最高有效位
LSB:Least Significant Bit ------- 最低有效位

处理器在硬件上由于端模式问题在设计中有所不同。从系统的角度上看,端模式问题对软件和硬件的设计带来了不同的影响,当一个处理器系统中大小端模式同时存在时,必须要对这些不同端模式的访问进行特殊的处理。
PowerPC处理器主导网络市场,可以说绝大多数的通信设备都使用PowerPC处理器进行协议处理和其他控制信息的处理,这也可能也是在网络上的绝大多数协议都采用大端编址方式的原因。因此在有关网络协议的软件设计中,使用小端方式的处理器需要在软件中处理端模式的转变。而Pentium主导个人机市场,因此多数用于个人机的外设都采用小端模式,包括一些在网络设备中使用的PCI总线,Flash等设备,这也要求在硬件设计中注意端模式的转换。
本文提到的小端外设是指这种外设中的寄存器以小端方式进行存储,如PCI设备的配置空间,NOR FLASH中的寄存器等等。对于有些设备,如DDR颗粒,没有以小端方式存储的寄存器,因此从逻辑上讲并不需要对端模式进行转换。在设计中,只需要将双方数据总线进行一一对应的互连,而不需要进行数据总线的转换。
如果从实际应用的角度说,采用小端模式的处理器需要在软件中处理端模式的转换,因为采用小端模式的处理器在与小端外设互连时,不需要任何转换。而采用大端模式的处理器需要在硬件设计时处理端模式的转换。大端模式处理器需要在寄存器,指令集,数据总线及数据总线与小端外设的连接等等多个方面进行处理,以解决与小端外设连接时的端模式转换问题。在寄存器和数据总线的位序定义上,基于大小端模式的处理器有所不同。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值