1.什么是大小端?
这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为 8bit。但是在C语言中除了8bit的char之外,还有16bit的short型,32bit的long型(要看具体的编译器),另外,对于位数大于 8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。
2.大小端定义
大端模式:即把数据的高字节放到低地址中
小端模式:高字节放到高地址中
3.图解
画张图简单解释下大小端的区别,比如我们要存取一个0x12345678的数据,在大小端机器的存取方式分别是:
大端模式:
低地址 -----------------> 高地址
0x12 | 0x34 | 0x56 | 0x78
小端模式:
低地址 ------------------> 高地址
0x78 | 0x56 | 0x34 | 0x12
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;
}
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;
}
6.大小端问题的来源
先补充两个关键词,MSB和LSB:MSB:MoST Significant Bit
------- 最高有效位LSB:Least Significant Bit
------- 最低有效位
处理器在硬件上由于端模式问题在设计中有所不同。从系统的角度上看,端模式问题对软件和硬件的设计带来了不同的影响,当一个处理器系统中大小端模式同时存在时,必须要对这些不同端模式的访问进行特殊的处理。
PowerPC处理器主导网络市场,可以说绝大多数的通信设备都使用PowerPC处理器进行协议处理和其他控制信息的处理,这也可能也是在网络上的绝大多数协议都采用大端编址方式的原因。因此在有关网络协议的软件设计中,使用小端方式的处理器需要在软件中处理端模式的转变。而Pentium主导个人机市场,因此多数用于个人机的外设都采用小端模式,包括一些在网络设备中使用的PCI总线,Flash等设备,这也要求在硬件设计中注意端模式的转换。
本文提到的小端外设是指这种外设中的寄存器以小端方式进行存储,如PCI设备的配置空间,NOR FLASH中的寄存器等等。对于有些设备,如DDR颗粒,没有以小端方式存储的寄存器,因此从逻辑上讲并不需要对端模式进行转换。在设计中,只需要将双方数据总线进行一一对应的互连,而不需要进行数据总线的转换。
如果从实际应用的角度说,采用小端模式的处理器需要在软件中处理端模式的转换,因为采用小端模式的处理器在与小端外设互连时,不需要任何转换。而采用大端模式的处理器需要在硬件设计时处理端模式的转换。大端模式处理器需要在寄存器,指令集,数据总线及数据总线与小端外设的连接等等多个方面进行处理,以解决与小端外设连接时的端模式转换问题。在寄存器和数据总线的位序定义上,基于大小端模式的处理器有所不同。