前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >C语言-- 大端小端详解

C语言-- 大端小端详解

作者头像
用户3479834
发布2021-02-03 15:46:55
3.2K0
发布2021-02-03 15:46:55
举报
文章被收录于专栏:游戏开发司机

一、什么是大端和小端

所谓的大端模式,就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。

所谓的小端模式,就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。

简单来说:大端——高尾端,小端——低尾端

举个例子,比如数字 0x12 34 56 78在内存中的表示形式为:

1)大端模式:

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

0x12 | 0x34 | 0x56 | 0x78

2)小端模式:

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

0x78 | 0x56 | 0x34 | 0x12

可见,大端模式和字符串的存储模式类似。

3)下面是两个具体例子:

16bit宽的数0x1234在Little-endian模式(以及Big-endian模式)CPU内存中的存放方式(假设从地址0x4000开始存放)为:

内存地址

小端模式存放内容

大端模式存放内容

0x4000

0x34

0x12

0x4001

0x12

0x34

32bit宽的数0x12345678在Little-endian模式以及Big-endian模式)CPU内存中的存放方式(假设从地址0x4000开始存放)为:

内存地址

小端模式存放内容

大端模式存放内容

0x4000

0x78

0x12

0x4001

0x56

0x34

0x4002

0x34

0x56

0x4003

0x12

0x78

4)大端小端没有谁优谁劣,各自优势便是对方劣势:

小端模式 :强制转换数据不需要调整字节内容,1、2、4字节的存储方式一样。 大端模式 :符号位的判定固定为第一个字节,容易判断正负。

二、数组在大端小端情况下的存储:

  以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) -- 低位 -------------- 低地址

三、为什么会有大小端模式之分呢?

这是因为在计算机中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为 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都为小端模式。有些ARM处理器还可以由硬件来选择是大端模式还是小端模式。

四、如何判断机器的字节序 (重点)

一般都是通过 union 来测试的,下面这段代码可以用来测试一下你的编译器是大端模式还是小端模式:

代码语言:javascript
复制
#include <stdio.h>int main (void){	union	{		short i;		char a[2];	}u;	u.a[0] = 0x11;	u.a[1] = 0x22;	printf ("0x%x\n", u.i);  //0x2211 为小端  0x1122 为大端	return 0;}输出结果:0x2211

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

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

当然你也可以这样

代码语言:javascript
复制
#include <stdio.h>int main (void){	short i = 0x1122;	char *a = (char*)(&i);	printf ("0x%x\n", *(a + 0)); //大端为 0x11 小端为 0x22	printf ("0x%x\n", *(a + 1));	return 0;}输出结果:0x220x11

说明:上面两个例子,可以通过 if 语句来判断大小端,这里只是介绍方法。

五、常见的字节序

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

1)常见CPU的字节序

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

2)常见文件的字节序

Adobe PS – Big Endian BMP – Little Endian DXF(AutoCAD) – Variable GIF – Little Endian JPEG – Big Endian MacPaint – Big Endian RTF – Little Endian

另外,Java和所有的网络通讯协议都是使用Big-Endian的编码。

六、如何进行大小端转换(重点)

第一种方法:位操作

代码语言:javascript
复制
#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 ;}输出结果:------------带参宏-------------0x34120x78563412------------函数调用-----------0x34120x78563412

这里有个思考?上面的哪个是转换为大端,哪个是转为小端了呢?

举个例子,比如数字 0x12 34 56 78在内存中的表示形式为:

1)大端模式:

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

0x12 | 0x34 | 0x56 | 0x78

2)小端模式:

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

0x78 | 0x56 | 0x34 | 0x12

则:

转换为大端:

pPack[2] = (u8)((len >> 8) & 0xFF); pPack[3] = (u8)(len & 0xFF);

转为为小端:

pPack[2] = (u8)(len & 0xFF);

pPack[3] = (u8)((len >> 8) & 0xFF);

第二种方法:

从软件的角度理解端模式

使用 htonl, htons, ntohl, ntohs 等函数 这个可以参考我的网络编程部分的知识第一节 深入浅出TCPIP之理解TCP报文格式和交互流程

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

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

举两个小例子:

代码语言:javascript
复制
//示例一#include <stdio.h>#icnlude <arpa/inet.h>int main (void){	union	{		short i;		char a[2];	}u;	u.a[0] = 0x11;	u.a[1] = 0x22;	printf ("0x%x\n", u.i);  //0x2211 为小端  0x1122 为大端	printf ("0x%.x\n", htons (u.i)); //大小端转换	return 0;}输出结果:0x22110x1122
代码语言:javascript
复制
//示例二#include <stdio.h>#include <arpa/inet.h>struct ST{short val1;short val2;};union U{int val;struct ST st;};
int main(void){int a = 0;union U u1, u2;
a = 0x12345678;u1.val = a;printf("u1.val is 0x%x\n", u1.val);printf("val1 is 0x%x\n", u1.st.val1);printf("val2 is 0x%x\n", u1.st.val2);printf("after first convert is: 0x%x\n", htonl(u1.val));u2.st.val2 = htons(u1.st.val1);u2.st.val1 = htons(u1.st.val2);printf("after second convert is: 0x%x\n", u2.val);return 0;}输出结果:u1.val is 0x12345678val1 is 0x5678val2 is 0x1234after first convert is: 0x78563412after second convert is: 0x78563412

在对普通文件进行处理也需要考虑端模式问题。在大端模式的处理器下对文件的32,16位读写操作所得到的结果与小端模式的处理器不同。单纯从软件的角度理解上远远不能真正理解大小端模式的区别。事实上,真正的理解大小端模式的区别,必须要从系统的角度,从指令集,寄存器和数据总线上深入理解,大小端模式的区别。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-12-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 游戏开发司机 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、什么是大端和小端
  • 二、数组在大端小端情况下的存储:
  • 三、为什么会有大小端模式之分呢?
  • 四、如何判断机器的字节序 (重点)
  • 五、常见的字节序
  • 六、如何进行大小端转换(重点)
相关产品与服务
对象存储
对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档