字节序、大端序与小端序及其相关转换

2024-03-01 21:48

本文主要是介绍字节序、大端序与小端序及其相关转换,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

大端序与小端序及其相关转换

 

一、字节序定义

字节序,为字节的顺序,就是大于一个字节类型的数据在内存中的存放顺序,一个字节的数据当然就没有顺序可言了,其实大部分人在实际的开发中都很少会直接和字节序打交道。唯有在跨平台以及网络程序中字节序才是一个应该被考虑的问题。

二、大端序与小端序

字节序分为两类:Big-Endian和Little-Endian。

1. Little-Endian(小端序)就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。
2. Big-Endian(大端序)就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。

unsigned int整型数据0x12345678为例,其大端序、小端序的存储内容如图所示

如0x01000000就为内存的低地址端,0x01000003就为内存的高地址端

而数据0x12345678中靠近左边的为高位字节,靠近右边的为低位字节,也就是说0x12为高位字节,0x78为低位字节

	unsigned int i= 0x12345678;unsigned char* p = (unsigned char*)&i;printf("%x\n", p[0]); //打印出16进制的78printf("%x\n", p[3]); //打印出16进制的12

如代码所示,对 i 的地址进行类型转换,指针p指向的位置是 i 在内存中存储空间的低地址端的位置,又因为个人电脑内存中存储数据是以小端序来存储的,所以内存低地址端存的是低位字节,所以p[0]以16进制来打印,打印出78,p[3]打印出12

三、网络字节序

 网络字节序:4个字节的32 bit值以下面的次序传输:首先是0~7bit,其次8~15bit,然后16~23bit,最后是24~31bit。这种传输次序称作大端字节序。由于 TCP/IP首部中所有的二进制整数在网络中传输时都要求以这种次序,因此它又称作网络字节序。比如,以太网头部中2字节的“以太网帧类型”,表示后面数据的类型。UDP/TCP/IP协议规定:把接收到的第一个字节当作高位字节看待,这就要求发送端发送的第一个字节是高位字节;而在发送端发送数据时,发送的第一个字节是该数值在内存中的起始地址处对应的那个字节,也就是说,该数值在内存中的起始地址处对应的那个字节就是要发送的第一个高位字节(即:高位字节存放在低地址处);由此可见,多字节数值在发送之前,在内存中因该是以大端法存放的; 所以说,网络字节序是大端字节序; 比如,我们经过网络发送整型数值0x12345678时,在80X86平台中,它是以小端发存放的,在发送之前需要使用系统提供的字节序转换函数htonl()将其转换成大端法存放的数值。对于ARP请求或应答的以太网帧类型来说,在网络传输时,发送的顺序是0x08,0x06。在内存中的映象如下图所示:

栈底 (高地址)
---------------
0x06 -- 低位字节 
0x08 -- 高位字节
---------------
栈顶 (低地址)
该字段的值为0x0806。按照大端方式存放在内存中。

四、内存空间中的相关布局

关于内存空间布局情况的说明,大致如下图:

----------------------- 最高内存地址 0xffffffff
 | 栈底
 .
 .              栈
 .
  栈顶
-----------------------
 |
 |
\|/

NULL (空洞)

/|\
 |
 |
-----------------------
                堆
-----------------------
未初始化的数据
----------------(统称数据段)
初始化的数据
-----------------------
正文段(代码段)
----------------------- 最低内存地址 0x00000000

以上图为例如果我们在栈上分配一个unsigned char buf[4],那么这个数组变量在栈上是如何布局的呢?看下图:
栈底 (高地址)
----------
buf[3]
buf[2]
buf[1]
buf[0]
----------
栈顶 (低地址)

现在我们弄清了高低地址,接着来弄清高/低字节,如果我们有一个32位无符号整型0x12345678,那么高位是什么,低位又是什么呢?其实很简单。在十进制中我们都说靠左边的是高位,靠右边的是低位,在其他进制也是如此。就拿 0x12345678来说,从高位到低位的字节依次是0x12、0x34、0x56和0x78。

高低地址和高低字节都弄清了。我们再来回顾一下Big-Endian和Little-Endian的定义,并用图示说明两种字节序:
以unsigned int value = 0x12345678为例,分别看看在两种字节序下其存储情况,我们可以用unsigned char buf[4]来表示value:

Big-Endian: 低地址存放高位,如下图:
栈底 (高地址)
---------------
buf[3] (0x78) -- 低位字节
buf[2] (0x56)
buf[1] (0x34)
buf[0] (0x12) -- 高位字节
---------------
栈顶 (低地址)

Little-Endian: 低地址存放低位,如下图:
栈底 (高地址)
---------------
buf[3] (0x12) -- 高位字节
buf[2] (0x34)
buf[1] (0x56)
buf[0] (0x78) -- 低位字节
---------------
栈顶 (低地址)

在现有的平台上Intel的X86采用的是Little-Endian,而像Sun的SPARC采用的就是Big-Endian。

五、网络通讯字节的转换

 相同字节序的平台在进行网络通信时可以不进行字节序转换,但是跨平台进行网络数据通信时必须进行字节序转换。

 原因如下:网络协议规定接收到得第一个字节是高字节,存放到低地址所以发送时会首先去低地址取数据的高字节。小端模式的多字节数据在存放时,低地址存放的是低字节,而被发送方网络协议函数发送时会首先去低地址取数据(想要取高字节,真正取得是低字节),接收方网络协议函数接收时会将接收到的第一个字节存放到低地址(想要接收高字节,真正接收的是低字节),所以最后双方都正确的收发了数据。而相同平台进行通信时,如果双方都进行转换最后虽然能够正确收发数据,但是所做的转换是没有意义的,造成资源的浪费。而不同平台进行通信时必须进行转换,不转换会造成错误的收发数据,字节序转换函数会根据当前平台的存储模式做出相应正确的转换。当前平台为大端

六、大小端序的判断

(1)通过指针判断(返回真则为小端序,返回假则为大端序)

bool isLittleEndian()
{unsigned int i = 0x12345678;unsigned char* c = (unsigned char*)&i;return(*c == 0x78); //判断是否是低位字节存内存低地址  *c取得就是内存中存放i的低地址
}

(2)通过联合体判断(返回真则为小端序,返回假则为大端序)

bool isLittleEndian()
{union{int i;char c;}udata;udata.i = 1;return(udata.c == 1);
}

(3)linux环境下,通过htonl等函数直接判断

#include <arpa/inet.h>
bool isLittleEndian()
{return (1 != htonl(1));
}

七、大小端的转换

(1)自定义函数

//短整型大小端互换
#define BigLittleSwap16(A) ((((uint16_t)(A) & 0xff00) >> 8 ) | \\(((uint16_t)(A) & 0x00ff) << 8 ))//长整型大小端互换
#define BigLittleSwap32(A) ((((uint32_t)(A) & 0xff000000) >> 24) | \\(((uint32_t)(A) & 0x00ff0000) >> 8 ) | \\(((uint32_t)(A) & 0x0000ff00) << 8 ) | \\(((uint32_t)(A) & 0x000000ff) << 24))

结合判断大小端的函数,如果本机是大端,则可以直接返回,如果本机是小端,则需要进行字节序的转换,或者进行网络数据的转换,再返回

(2)winsock.h头文件中的函数

uint32_t htonl(uint32_t hostlong);//32位的主机字节序转换到网络字节序
uint16_t htons(uint16_t hostshort);//16位的主机字节序转换到网络字节序
uint32_t ntohl(uint32_t netlong);//32位的网络字节序转换到主机字节序
uint16_t ntohs(uint16_t netshort);//16位的网络字节序转换到主机字节序

拿htonl和ntohl来分析,htonl函数的内部实现原理是这样,先判断主机是什么模式存储,如果是大端模式,就跟网络字节序一致,直接返回参数即可,如果是小端模式,则把形参转换成大端模式存储在一个临时参数内,再把临时参数返回;而ntohl函数的实现原理也是一样的过程,但是要注意它的参数,参数是网络字节序,就是大端模式存储,因此当判断主机是大端模式的时候,会直接返回,因为该函数默认会认为形参是网络字节序,把它当大端模式来看,如果判断主机是小端模式,就会将实参做转换,转换的过程并不复杂,就是逆序存储各个字节的数据,所以结果就被转换。

说到这里,可以看出一个规律来,就是如果主机与网络字节序不一致(也就是小端模式),这四个函数的返回值与传递进去的实参值的字节排序肯定是逆序的,所以返回值绝对不等于实参值,例如htonl(1)的结果肯定不是1,而如果主机与网络字节序一致(也就是大端模式),则这四个函数根本就没有做转换操作,而是直接返回实参值,这样他们的返回结果就肯定与实参值相同,即htonl(1)的结果是1。

这样,我们就得到了一个非常简便的判断系统是什么模式的方法,就是直接利用这四个函数来判断,如:

if(1 != htonl(1)) 
{//小端模式,作相应处理
}
else 
{//大端模式,作相应处理
}

或者直接用一个判断if(1 != htonl(1)),只有主机字节序与网络字节序不一致时,才调用那些函数去转换,否则不需要处理,这样可以减少多余的函数调用。

八、其他相关转化

(1)将整数按照“大端序”格式存储在数组中

/*
*    Function:        ConverseUItoBeArray
*    Description:    将无符号整数转换成“大端序”存储的无符号字符数组
*    Parameter:        srcData        --[in]    源整数
*                    desBeArray       --[out]    目标“大端序”存储的数组数据
*    Return            0    成功
*                    非0    失败
*    Note:            
*    Other:
*/
int MULCONVERSE_CALL ConverseUItoBeArray(unsigned int srcData,unsigned char *desBeArray)
{if (desBeArray == NULL_POINT){return ERR_NULL_POINT;}desBeArray[0] = (unsigned char)(srcData>>24);desBeArray[1] = (unsigned char)(srcData>>16);desBeArray[2] = (unsigned char)(srcData>>8);desBeArray[3] = (unsigned char)srcData;return _SUCCESS;
}

(2)将整数按照“小端序”格式存储在数组中

/*
*    Function:        ConverseUItoLeArray
*    Description:    将无符号整数转换成“小端序”存储的无符号字符数组
*    Parameter:        srcData        --[in]    源整数
*                    desLeArray       --[out]   目标“小端序”存储的数组数据
*    Return            0    成功
*                    非0    失败
*    Note:            
*    Other:
*/
int MULCONVERSE_CALL ConverseUItoLeArray(unsigned int srcData,unsigned char *desLeArray)
{if (desLeArray == NULL_POINT){return ERR_NULL_POINT;}desLeArray[3] = (unsigned char)(srcData>>24);desLeArray[2] = (unsigned char)(srcData>>16);desLeArray[1] = (unsigned char)(srcData>>8);desLeArray[0] = (unsigned char)srcData;return _SUCCESS;
}

 

这篇关于字节序、大端序与小端序及其相关转换的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/763935

相关文章

Java实现将Markdown转换为纯文本

《Java实现将Markdown转换为纯文本》这篇文章主要为大家详细介绍了两种在Java中实现Markdown转纯文本的主流方法,文中的示例代码讲解详细,大家可以根据需求选择适合的方案... 目录方法一:使用正则表达式(轻量级方案)方法二:使用 Flexmark-Java 库(专业方案)1. 添加依赖(Ma

Java实现将byte[]转换为File对象

《Java实现将byte[]转换为File对象》这篇文章将通过一个简单的例子为大家演示Java如何实现byte[]转换为File对象,并将其上传到外部服务器,感兴趣的小伙伴可以跟随小编一起学习一下... 目录前言1. 问题背景2. 环境准备3. 实现步骤3.1 从 URL 获取图片字节数据3.2 将字节数组

Java中数组转换为列表的两种实现方式(超简单)

《Java中数组转换为列表的两种实现方式(超简单)》本文介绍了在Java中将数组转换为列表的两种常见方法使用Arrays.asList和Java8的StreamAPI,Arrays.asList方法简... 目录1. 使用Java Collections框架(Arrays.asList)1.1 示例代码1.

Python使用PIL库将PNG图片转换为ICO图标的示例代码

《Python使用PIL库将PNG图片转换为ICO图标的示例代码》在软件开发和网站设计中,ICO图标是一种常用的图像格式,特别适用于应用程序图标、网页收藏夹图标等场景,本文将介绍如何使用Python的... 目录引言准备工作代码解析实践操作结果展示结语引言在软件开发和网站设计中,ICO图标是一种常用的图像

Python3脚本实现Excel与TXT的智能转换

《Python3脚本实现Excel与TXT的智能转换》在数据处理的日常工作中,我们经常需要将Excel中的结构化数据转换为其他格式,本文将使用Python3实现Excel与TXT的智能转换,需要的可以... 目录场景应用:为什么需要这种转换技术解析:代码实现详解核心代码展示改进点说明实战演练:从Excel到

Java数字转换工具类NumberUtil的使用

《Java数字转换工具类NumberUtil的使用》NumberUtil是一个功能强大的Java工具类,用于处理数字的各种操作,包括数值运算、格式化、随机数生成和数值判断,下面就来介绍一下Number... 目录一、NumberUtil类概述二、主要功能介绍1. 数值运算2. 格式化3. 数值判断4. 随机

C语言中自动与强制转换全解析

《C语言中自动与强制转换全解析》在编写C程序时,类型转换是确保数据正确性和一致性的关键环节,无论是隐式转换还是显式转换,都各有特点和应用场景,本文将详细探讨C语言中的类型转换机制,帮助您更好地理解并在... 目录类型转换的重要性自动类型转换(隐式转换)强制类型转换(显式转换)常见错误与注意事项总结与建议类型

Python实现视频转换为音频的方法详解

《Python实现视频转换为音频的方法详解》这篇文章主要为大家详细Python如何将视频转换为音频并将音频文件保存到特定文件夹下,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1. python需求的任务2. Python代码的实现3. 代码修改的位置4. 运行结果5. 注意事项

使用Python实现图片和base64转换工具

《使用Python实现图片和base64转换工具》这篇文章主要为大家详细介绍了如何使用Python中的base64模块编写一个工具,可以实现图片和Base64编码之间的转换,感兴趣的小伙伴可以了解下... 简介使用python的base64模块来实现图片和Base64编码之间的转换。可以将图片转换为Bas

Redis的Zset类型及相关命令详细讲解

《Redis的Zset类型及相关命令详细讲解》:本文主要介绍Redis的Zset类型及相关命令的相关资料,有序集合Zset是一种Redis数据结构,它类似于集合Set,但每个元素都有一个关联的分数... 目录Zset简介ZADDZCARDZCOUNTZRANGEZREVRANGEZRANGEBYSCOREZ