架构师必须要掌握的大小端问题

本文涉及的产品
无影云电脑企业版,4核8GB 120小时 1个月
资源编排,不限时长
无影云电脑个人版,黄金款:40核时/1个月有效
简介: 架构师必须要掌握的大小端问题

一、什么是大端和小端

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

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

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

举个例子,比如数字 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 来测试的,下面这段代码可以用来测试一下你的编译器是大端模式还是小端模式:

#include <stdio.h>intmain (void)
{
  union
  {
    short i;
    char a[2];
  }u;
  u.a[0] = 0x11;
  u.a[1] = 0x22;
  printf ("0x%x\n", u.i);  //0x2211 为小端  0x1122 为大端
  return0;
}
输出结果:
0x2211

image.gif

image.gif编辑

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

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

当然你也可以这样

#include <stdio.h>intmain (void)
{
  short i = 0x1122;
  char *a = (char*)(&i);
  printf ("0x%x\n", *(a + 0)); //大端为 0x11 小端为 0x22
  printf ("0x%x\n", *(a + 1));
  return0;
}
输出结果:
0x220x11

image.gif

image.gif编辑

说明:上面两个例子,可以通过 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的编码。

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

第一种方法:位操作

#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) ;
}
intmain(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));
return0 ;
}
输出结果:
------------带参宏-------------
0x34120x78563412
------------函数调用-----------
0x34120x78563412

image.gif

image.gif编辑

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

举个例子,比如数字 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);

举两个小例子:

//示例一#include <stdio.h>#icnlude <arpa/inet.h>intmain (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)); //大小端转换
  return0;
}
输出结果:
0x22110x1122

image.gif

image.gif编辑

//示例二#include <stdio.h>#include <arpa/inet.h>
struct ST{
short val1;
short val2;
};
union U{
int val;
struct ST st;
};
intmain(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);
return0;
}
输出结果:
u1.val is 0x12345678
val1 is 0x5678
val2 is 0x1234
after first convert is: 0x78563412
after second convert is: 0x78563412

image.gif

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

相关文章
|
5月前
|
C++
C/C++工程师面试题(指针篇)
C/C++工程师面试题(指针篇)
85 0
|
5月前
|
存储 小程序 编译器
c语言内功修炼--深度剖析数据的存储
c语言内功修炼--深度剖析数据的存储
|
4月前
|
C语言
C语言中求x的n次方:从入门到实践(保姆式教学)
C语言中求x的n次方:从入门到实践(保姆式教学)
148 0
|
5月前
|
存储 小程序 编译器
C语言进阶⑩(数据的存储)数据类型_介绍+存储_大小端(知识点+笔试题)(上)
C语言进阶⑩(数据的存储)数据类型_介绍+存储_大小端(知识点+笔试题)
39 0
|
5月前
|
存储 C语言
C语言进阶⑩(数据的存储)数据类型_介绍+存储_大小端(知识点+笔试题)(下)
C语言进阶⑩(数据的存储)数据类型_介绍+存储_大小端(知识点+笔试题)
39 0
|
5月前
|
存储 编译器 C语言
C语言进阶⑩(数据的存储)数据类型_介绍+存储_大小端(知识点+笔试题)(中)
C语言进阶⑩(数据的存储)数据类型_介绍+存储_大小端(知识点+笔试题)
36 0
|
10月前
|
架构师 前端开发 Java
自从读了字节技术总监的架构师成长指南,面试像开挂“百发百中”
最近有幸在一位字节跳动技术总监手里扒到了这份架构师成长指南,将部分知识章节发布到了在知乎上竟然获得了5000+点赞!
|
11月前
|
存储 编译器 C语言
【C语言航路】第十站:指针(三)深刻理解指针运算
【C语言航路】第十站:指针(三)深刻理解指针运算
43 0
|
12月前
|
存储 网络协议 C语言
C语言进阶教程(大小端存储)
C语言进阶教程(大小端存储)
81 0
|
存储 小程序 算法
你是真的“C”——【经典面试知识点】数据在内存中的大小端存储方式
前言🙌 大小端介绍🙌 什么大端小端呢?: 大小端存储的标准定义: 大端和小端存在的意义 经典的面试题目🙌 总结撒花💞
125 0