嗨喽,大家好,我是程序猿老王,程序老王就是我。
今天给大家讲一讲工作中经常遇到的大小端模式转换问题。
首先先来了解一下为什么会存在大小端模式转换
是因为在计算机中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为 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都为小端模式。
大小端定义
不同机器内部对变量的字节存储顺序不同,有的采用大端模式(big-endian),有的采用小端模式(little-endian)。
大端模式是指高字节数据存放在低地址处,低字节数据放在高地址处。
小端模式是指低字节数据存放在低地址处,高字节数据放在高地址处。
下面给大家看一个示例就知道了什么是大小端了:
0x12345678在小端模式内存中的表示形式:
内存 低地址 -----------------> 高地址
0x78 | 0x56 | 0x34 | 0x12
低字节 -----------------> 高字节
0x12345678在大端模式内存中的表示形式:
内存 低地址 -----------------> 高地址
0x12 | 0x34 | 0x56 | 0x78
高字节 -----------------> 低字节
大小端模式转换方法
在网络上传输数据时,由于数据传输的两端可能对应不同的硬件平台,采用的存储字节顺序也可能不一致,因此 TCP/IP 协议规定了在网络上必须采用网络字节顺序(也就是大端模式) 。
通过对大小端的存储原理分析可发现,对于 char 型数据,由于其只占一个字节,所以不存在这个问题,这也是一般情况下把数据缓冲区定义成 char 类型 的原因之一。对于 IP 地址、端口号等非 char 型数据,必须在数据发送到网络上之前将其转换成大端模式,在接收到数据之后再将其转换成符合接收端主机的存储模式。
Linux 系统为大小端模式的转换提供了 4 个函数,函数原型:
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
htonl 表示 host to network long,用于将主机 unsigned int 型数据转换成网络字节顺序;
htons 表示 host to network short,用于将主机 unsigned short 型数据转换成网络字节顺序;
ntohl、ntohs 的功能分别与 htonl、htons 相反。
最后再给大家展示一下模拟实现大小端转换函数
typedef unsigned short int uint16;
typedef unsigned long int uint32;
/*短整型大小端互换*/
#define BigLittleSwap16(A) ((((uint16)(A) & 0xff00) >> 8) | \
(((uint16)(A) & 0x00ff) << 8))
/*长整型大小端互换*/
#define BigLittleSwap32(A) ((((uint32)(A) & 0xff000000) >> 24) | \
(((uint32)(A) & 0x00ff0000) >> 8) | \
(((uint32)(A) & 0x0000ff00) << 8) | \
(((uint32)(A) & 0x000000ff) << 24))
/*本机大端返回1,小端返回0*/
int checkCPUendian(void)
{
union{
unsigned long int i;
unsigned char s[4];
}c;
c.i = 0x12345678;
return (0x12 == c.s[0]);
}
/*模拟htonl函数,本机字节序转网络字节序*/
unsigned long int t_htonl(unsigned long int h)
{
/*若本机为大端,与网络字节序同,直接返回*/
/*若本机为小端,转换成大端再返回*/
return checkCPUendian() ? h : BigLittleSwap32(h);
}
/*模拟ntohl函数,网络字节序转本机字节序*/
unsigned long int t_ntohl(unsigned long int n)
{
/*若本机为大端,与网络字节序同,直接返回*/
/*若本机为小端,网络数据转换成小端再返回*/
return checkCPUendian() ? n : BigLittleSwap32(n);
}
/*模拟htons函数,本机字节序转网络字节序*/
unsigned short int t_htons(unsigned short int h)
{
/*若本机为大端,与网络字节序同,直接返回*/
/*若本机为小端,转换成大端再返回*/
return checkCPUendian() ? h : BigLittleSwap16(h);
}
/*模拟ntohs函数,网络字节序转本机字节序*/
unsigned short int t_ntohs(unsigned short int n)
{
/*若本机为大端,与网络字节序同,直接返回*/
/*若本机为小端,网络数据转换成小端再返回*/
return checkCPUendian() ? n : BigLittleSwap16(n);
}
-END-
关于更多嵌入式C语言、FreeRTOS、RT-Thread、Linux应用编程、linux驱动等相关知识,关注公众号【嵌入式Linux知识共享】,后续精彩内容及时收看了解。