博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
CPU字节序
阅读量:4115 次
发布时间:2019-05-25

本文共 6822 字,大约阅读时间需要 22 分钟。

一、字节序的起源

在计算机中,字节序()是数据中单独的可取地址的亚型(words,bytes和bits)在外部存储器中存储的顺序。通常在提到四字(ddword)、双字(dword)和字(word)的时候需要考虑其实际的字节顺序,为了简便起见它的英文也常常表示为Byte Order。

Endianness这个词源自1726年Jonathan Swift的名著:Gulliver’s Travels(格列佛游记),在书中有一个故事,大意是指Lilliput(小人国)的领导下了一道指令,规定其人民在剥水煮蛋时必须从little-end(小的那一端)开始。这个规定惹恼了一群觉得应该要从big-end(大的那一刻)开始剥的人。事情发展到后来,竟然演变成一场纷战。支持小的那端的人被称为little-endian,反之则被称为big-endian(在英语中后缀“-ian”表示“xx人”的意思)。1980年Danny Cohen在他的论文“On Holy Wars and a Plea for Peace”中第一次使用了Big-和Little-这两个术语,最终它们成为了计算机通过网络与其他计算机连接时所要考虑的极其重要的一个问题。

二、字节序的种类和其表示

那么为什么要引入字节序呢。我们都知道,计算机存储中最小的单位是位(bit),而8bit构成一个字节(byte)。在一个32位的CPU中,字长为32bit,也就是4byte,数据要想存放在内存中供CPU读取和写入,就需要拥有一定的存放顺序。这样不同的CPU可接受的字节序有可能不同,那么在设计硬件和软件时数据的存放问题也需要分开考虑。

数据都有所谓的“有效位(Significant Bit)”,顾名思义它表示了“数据存放有效的位置”,而字节序的分类就是依赖于有效位来进行划分的。在一个字节当中,数据的有效位的顺序已经得到了大多数硬件生产商的共识,那就是最高有效位优先(Most Significant Bit First),例如我们用8位二进制数来表示十进制数123为01111011,其第一位的0就是最高有效位,而最后一位的1就是最低有效位,在一个字节当中,几乎当前所有的硬件都采用了这种直观的字节序。

然而情况在离开了单字节时就有所不同了。不同的硬件产商对于数据占据多个字节时拥有怎样的字节序有着不同的理解,具体说来分为以下三类:

  • Big-Endian(大字节序):最高有效字节优先,更高的字节有效位占据着更低地址的内存空间,其在内存中的表示与直观吻合,
  • Little-Endian(小字节序):最低有效字节优先,更低的字节有效位占据着更低地址的内存空间,其在内存中的表示与直观相反,以及
  • Mixed-Endian(混合字节序)或者Middle-Endian(中字节序):在16位字(word)中的字节序与32位字(dword)中的字节序不相同。这种类型的字节序较为少见。

一些知名的使用Little-Endian的处理器体系结构包括了:x86、6502、Z80、VAX以及PDP-11,使用Big-Endian的处理器通常是Motorola的处理器,例如:6800、68000、PowerPC(即Macintosh在迁移到x86之前所采用的处理器)以及System/370。这也是为什么在文章开头提到的文档中使用Big Endian / Motorola standard这样的词汇的原因。

更进一步的,像ARM、PowerPC、Alpha、SPARC V9、MIPS、PA-RISC和IA64等体系结构可以支持可切换的字节序这样的特性,这个特性可以提高效率或者简化网络设备和软件的逻辑。这种可切换的字节序被称为Bi-Endian,用于硬件上意指计算机或者传递数据时可以使用两种不同字节序中任意一种的能力。

文字不够直观,下面以数值0x0A0B0C0Dh为例说明Big-Endian和Little-Endian在内存布局上的不同:

  • Big-Endian在内存中的表示

increasing addresses  →
... 0Ah 0Bh 0Ch 0Dh ...

在这个例子中,最高有效字节(MSB)为0Ah,储存在最低地址的内存中;次高有效位为0Bh,储存在接下来的内存中,依此类推。这种字节序与从左向右的顺序读取十六进制数值非常类似。

以16位元素大小查看:

increasing addresses  →
... 0A0Bh 0C0Dh ...

最高有效元素现在保存的是0A0Bh,接下来的元素保存0C0Dh.

  • Little-Endian在内存中的表示

increasing addresses  →
... 0Dh 0Ch 0Bh 0Ah ...

在这个例子中,最低有效字节(LSB)的值为0Dh,储存在最低地址的内存,其他字节依照字节有效性的递增依次存放。

用16位元素大小表示

increasing addresses  →
... 0C0Dh 0A0Bh ...

最低有效16位单元储存的是值0C0Dh,紧接着储存值0A0Bh

三、字节序的重要性及其应用

如前所述,不同硬件的体系结构接受不同字节序的数据表示,因此当同一个文件在不同的机器中进行读取和写入的时候,其所支持的字节序就显得尤为关键。设想在x86计算机中将(123888)10写入二进制文件中,由于x86支持Little-Endian,所以该数在文件中保存为(0000003F1E)16。当在PowerPC计算机中读取该整数时,由于它支持的是Big-Endian,故读取的结果将是(16158)10,大相径庭。

同样的情况也会出现在网络传输当中,当你从支持一种字节序的机器发送数据到支持相反字节序的机器时,将会得到非预期的结果。这种错误在网络传输当中尤为突出,因为你无法决定发送你所需文件机器所支持的字节序,因为这些机器可能分散在世界各地,不是人为所能控制的。

为了更明确的说明上述问题,考虑下列代码:

Listing 1: Example
01 
#include <stdio.h>

02 
#include <string.h>

03

04 
int 
main 
(
int 
argc
, 
char
* 
argv
[]) 
{

05 
 
 
 
 
FILE
* 
fp;

06

07 
 
 
 
 

08 
 
 
 
 
struct 
{

09 
 
 
 
 
 
 
 
 
char 
one
[
4
];

10 
 
 
 
 
 
 
 
 
int 
 
two;

11 
 
 
 
 
 
 
 
 
char 
three
[
4
];

12 
 
 
 
 
} 
data;

13

14 
 
 
 
 

15 
 
 
 
 
strcpy 
(
data
.
one
, 
“foo”);

16 
 
 
 
 
data
.
two 
= 
0×01234567;

17 
 
 
 
 
strcpy 
(
data
.
three
, 
“bar”);

18

19 
 
 
 
 

20 
 
 
 
 
fp 
= 
fopen 
(
“output”
, 
“wb”);

21 
 
 
 
 
if 
(
fp
{

22 
 
 
 
 
 
 
 
 
fwrite 
(
&
data
, 
sizeof 
(
data
), 
1
, 
fp);

23 
 
 
 
 
 
 
 
 
fclose 
(
fp);

24 
 
 
 
 
}

25 
}

这是一段很简单的C语言代码,作用就是向一个data结构体赋值并且将它写入文件当中,从结果Listing 2和Listing 3当中我们就可以看到支持不同字节序的机器在处理数据时候存在的不同。

Listing 2. hexdump –C output on big-endian machines

00000000 66 6f 6f 00 01 23 45 67 62 61 72 00 |foo..#Egbar.| 0000000c

Listing 3. hexdump -C output on little-endian machines

00000000 66 6f 6f 00 67 45 23 01 62 61 72 00 |foo.gE#.bar.| 0000000c

注意力好的同学一眼就能发现,在写整数的时候,数据保存的顺序依赖于不同的机器,而字符串却不受此影响,这是为什么呢?这就牵涉到字节序是如何如代码进行影响的了。

字节序并不会影响数据存储的所有方面,例如对一个整数进行bitwise或者bitshift的操作,你是不需要去注意对应的字节序的。因为多字节的顺序是由计算机来维护的,对于程序员来说,一个整数的最低有效位仍然是最低有效位,最高有效位亦然,并不会由于它在计算机底层存储模式的改变而影响到有效位的含义。

同样的,字节序不会影响到C风格字符串在计算机底层的存储顺序,这是为什么呢?考虑到一个C风格字符串的实质是一个包含着许多char的数组,每一个char在现代计算机中几乎都是表示计算机中的一个字节。因此,当读写C风格字符串时,其最小的元素单位是一个字节;而且数组在内存单元中地址的排列顺序是递增的,例如定义char str[5];这么一条语句,假设&str[0]的地址为1000,则&str[1]的地址为1001,依次类推。所以不论从直观含义或者底层技术来看,字符串的存储都是相对字节序独立的,这个特性将应用在接下来的许多小技巧中。

那么字节序除了影响到多字节数据在内存中的存放顺序以外,在写代码的时候还有什么需要注意的呢?当对一个数据进行类型转换的时候,需要记住特定的字节序很可能影响到类型转换的结果。假设我们有Listing 4所列的这么一段代码

Listing 4: 强制类型转换
1 
unsigned 
char 
endian
[
2
] 
= 
{
1
, 
0
};
2 
short 
x;
3

4 
x 
= 
*(
short 
*
endian;

 

那么最后得到x的结果是多少呢?是不是简单的就是endian数组的第一个元素1呢?答案是错,x的数值需要根据运行时的环境来决定。让我们回忆一下C语言的指针指向多大的内存以及怎么去解释所指的这块内存是由指针所指向的类型来确定的,在上述代码中,将endian数组的首元素指针强制转换成short *的指针,那么编译器在解释它的时候将不再把它指向的内存空间视为1 byte,而是short的长度——2 byte;更重要的是当我们对这个指针解引用的时候将会得到的值会是什么。再回到上面所提到的字符串或者字符数组在计算机中就是依照数组顺序存放的,那么这个时候endian数组占用了两个字节,其内存数据为:0100。当该指针强制转换为指向short的指针并解引用时,计算机将一次读取两个字节,这个时候字节序就发挥它的影响了。在支持Little-Endian的机器中x的值将是1(读取为0001),而在支持Big-Endian的机器中x的值就是256(读取为0100)。因此在对指针进行类型转换并解引用,特别是在单字节到多字节数据的转换时,要特别注意字节序是否会使得预期结果出现偏差。

单字节指针到多字节指针的转换其实并不完全像Listing 4所举例子那样恼人,它还有其他的用途,例如我们可以使用这个特性在运行时判断当前计算机所支持的字节序,这样可以使得程序员在编写代码的时候更加灵活,也使得代码更加强健(robust)。基本的思路就是先定义一个int变量1,这个变量在不同的计算机中将有两种不同的存储顺序:01000000(Little)以及00000001(Big),然后我们将指向这个变量的指针强制转换为指向字符的指针,再解引用根据它的值是0还是1就可以得出当前机器支持的字节序的,代码很简单:

Listing 5: 判断字节序
1 
int 
i 
= 
1;
2
3 
if 
(
*(
char
*)
&
i 
== 
0)

 

4     // Big Endian

5 else

6     // Little Endian

=============================

不同体系结构的CPU,数据在内存中存放的排列顺序是不一样的。

存储器中对数据的存储是以字节(Byte)为基本单位的,因此,字(Word)和半字(Half-Word)在存储器中就有两种次序,分别称为:大端模式(Big Endian)小端模式(Little Endian)

大端存储模式是指字或半字的最高字节(Most Significant Bit,MSB)存放在内存的最低位字节地址上,而字数据的低字节则存放在高地址中。打个比方,有一个字为0x12345678,这个字由4个字节组成,从高位到低位的次序为:0x12,0x34,0x56,0x78。如果把这个字存放在以0x00000000起始的内存中,这个字在内存中的实际存放情况如下表:

内存地址

存储的数据(Byte)

0x00000000

0x12

0x00000001

0x34

0x00000002

0x56

0x00000003

0x78

0x00000004

……

大端模式的次序就像是我们平时书写的次序,先写大数,后写小数。另外,大端存储次序还广泛运用在TCP/IP协议上,因此又称为网络字节次序

小端存储模式是指字或半字的最低位字节(Lowest Significant Bit,LSB)存放在内存的最低位字节地址上,而字数据的高字节则存放在高地址中。还以0x12345678为例,在小端模式下存储如下表所示:

内存地址

存储的数据(Byte)

0x00000000

0x78

0x00000001

0x56

0x00000002

0x34

0x00000003

0x12

0x00000004

……

需要注意的几点是:

(1)   数据在寄存器中都是以大端模式次序存放的。

(2)   对于内存中以小端模式存放的数据。CPU存取数成时,小端和大端之间的转换是通过硬件实现的,没有数据加载/存储的开销。

===============================

字节序就讲的是占用多个字节的数据类型(int,long等)在内存中的存放顺序,比如一个int型,大小为109486163(为什么要用这个数举例?往下看就知道 了),用16进制的形式表示就是0x41424344,这个数在内存中是如何存放的呢,假设其址为0,这个int分配的地址也为0,那么,在地址0,1,2,3,上的值分别是多少?但愿这篇文章可以回答这个问题。

      字节序通常有小端、大端两种字节顺序。小端字节序指低字节数据存放在内存低地址处,高字节数据存放在内存高地址处;也就是低地址存放最低有效字节(LSB)。大端字节序是高字节数据存放在低地址处,低字节数据存放在高地址处。也就是是指低地址存放最高有效字节(MSB)

    下图显示了数据的0x41424344在两种字节顺序中的排列:

        大端字节顺序

 

     小端字节顺序

 

 

     基于X86平台的PC机是小端字节序的,而有的嵌入式平台则是大端字节序的。JAVA字节序也是(大端字节序)BIG-ENDIAN。

    下面用一个小程序来看看Intel使用的是是不是小端字节序(如果不是,那9成[留条后路先]是程序错了)。

    动态判断CPU字节序列:

    

 1
/************************************************************************
 2函数功能: 动态判断字节顺序
 3返回值  : 见枚举BYTEORDER定义
 4/************************************************************************/

 5
BYTEORDER GetByteOrder()
 6
{

 7    BYTEORDER eByteOrder = BO_UNKNOWN;
 8    union
 9    {

10        unsigned short int s;
11        unsigned char arr[sizeof (unsigned short int)];
12    }
un;
13    un.s = 0x1234;
14
15    if ( 0x12 == un.arr[0&& 0x34 == un.arr[1])
16    {

17        eByteOrder = BO_BIG_ENDIAN;
18    }

19    else if ( 0x34 == un.arr[0&& 0x12 == un.arr[1])
20    {

21        eByteOrder = BO_LITTLE_ENDIAN;
22    }

23    else
24    {

25        eByteOrder = BO_UNKNOWN;
26    }

27    return eByteOrder;
28}

 

    还记得一开始那个int值吗,现在我们要把这个int分解成4个char,分别是ABCD,并输出。如下图所示。程序就不贴了,需要请在附件中下载。

你可能感兴趣的文章
百度产品经理群面
查看>>
去哪儿一面+平安科技二面+hr面+贝贝一面+二面产品面经
查看>>
element ui 弹窗在IE11中关闭时闪现问题修复
查看>>
vue 遍历对象并动态绑定在下拉列表中
查看>>
Vue动态生成el-checkbox点击无法选中的解决方法
查看>>
python __future__
查看>>
MySQL Tricks1
查看>>
python 变量作用域问题(经典坑)
查看>>
pytorch
查看>>
pytorch(二)
查看>>
pytorch(三)
查看>>
pytorch(四)
查看>>
pytorch(5)
查看>>
pytorch(6)
查看>>
ubuntu相关
查看>>
C++ 调用json
查看>>
nano中设置脚本开机自启动
查看>>
动态库调动态库
查看>>
Kubernetes集群搭建之CNI-Flanneld部署篇
查看>>
k8s web终端连接工具
查看>>