大部分的数字(标量)值存储在计算机内存或 磁盘等存储介质都超过一个字节长,例如2字节 整数,8字节双精度浮点数等。这就产生了在机器上如何存储多字节的数据问题,每个字节都有它自己的地址,哪个字节首先存储在内存的低端位置,哪些字节跟随存储在高端位置?
假如两字节整数0x55FF存储在一个电脑的磁盘上,0x55 (高位字节)存储在内存低地址,0xFF (低位字节)存储在内存的高地址, 但是不同的机器读取的时候通过设置0xFF为高字节,0x55为低字节,0xFF55, 两种机器在整数值上不能达成一致!
本文地址:http://wuyudong.com/2016/08/06/2456.html,转载请注明源地址。
没有“正确”的顺序来存储多字节的数据。 硬件是建立处理特定顺序的字节, 只要兼容硬件读取字节顺序相同,事情将进展顺利。 我们将看到两个主要类型的字节次序:小端和大端。
小端
如果硬件建立后,一个多字节的标量的最低有效字节优先存储在内存的最低位,这样的硬件称为小端,整数的最低位最先存储,依次存储高位的字节,小端字节存储的字节顺序是最低位优先。
例如:Intel/AMD x86、Digital VAX 与 Digital Alpha,,以小端的形式处理数据
例如, 一个 4 字节的长整型
Byte3 Byte2 Byte1 Byte0
在内存中安排如下:
Base Address+0 Byte0 Base Address+1 Byte1 Base Address+2 Byte2 Base Address+3 Byte3
大端
如果硬件建立后,一个多字节的标量的最高有效字节优先存储在内存的最低位,这样的硬件称为大端,整数的最高位最先存储,依次存储低位的字节,大端字节存储的字节顺序是最高位优先。
例如:IBM 大型机,Motorola 680×0, Sun SPARC, PowerPC和大多数的 RISC指令机器,数据使用的是大端形式。
Dr. Bill’s Notes on “Little Endian” vs. “Big Endian”
上面的 4 字节的长整型在内存中安排如下:
Base Address+0 Byte3 Base Address+1 Byte2 Base Address+2 Byte1 Base Address+3 Byte0
四字节整数例子
研究一个四字节整数 0x44332211. 小端字节,最低位有效字节是 0x11, 最高位有效字节是 0x44. 下面是这个整数的两种存储模式:
四字节整数: 0x44332211 | ||
---|---|---|
内存地址 | 大端 | 小端 |
104 | 11 | 44 |
103 | 22 | 33 |
102 | 33 | 22 |
101 | 44 | 11 |
如果我们在memory dump中查看这四个字节, memory dump 通常在页面上从左到右展示相邻内存,按照递增的内存地址顺序。在dump内低内存地址在左边并且地址逐个递增到右边。
假如我们展示数字0x44332211,以内存地址101开始的大端顺序的memory dump, 将看到下面的:
ADDRESS: ---------- MEMORY BYTES ---------- 100: 00 44 33 22 11 00 00 00 00 00 ...
在大端存储顺序中,rder, the “big”, most significant, 字节0x44最先存储, 在最低内存位101,0x44从左到右的内存地址顺序: 102, 103, 104。dump 中的”44 33 22 11″输出顺序同书写0x44332211的顺序是相同的。
假如我们展示数字0x44332211,以内存地址101开始的小端顺序的memory dump, 将看到下面的:
ADDRESS: ---------- MEMORY BYTES ---------- 100: 00 11 22 33 44 00 00 00 00 00 ...
在小端存储顺序中,最低有效位,字节 0x11 最先存储,在最低存储位101。0x11在其它字节的左边,这些字节从左到右按照内存地址的顺序: 102, 103, 104递增。但是在dump中的字节输出顺序设计与我们读取数字的时候相反,硬件知道怎样将字节的正确顺序,但是对于人类而言,当我们看到小端dump的时候需要记住相应的规则。我们看到四字节在小端dump中的输出是”11 22 33 44″ ,需要将其倒序并重构成0x44332211.
注意: 当你看到一个小端字节顺序存储的多字节数据标量,总是颠倒这些字节数据标量的顺序。
字节顺序和字符数据
单字节数据例如ASCII和Latin-1不会受字节顺序的影响,如果你在内存存储任何ASCII字符串,无论硬件采用哪种端,通常看起来都是一致的,因为每一个字符是一个字节长,并且字符串的起始字符通常从最低内存位置开始。对于字符串”abcd”, “a”最先存储 “first”
四字节字符串: “abcd” | ||
---|---|---|
内存地址 | 字节数值 | ASCII 字符 |
104 | 64 | d |
103 | 63 | c |
102 | 62 | b |
101 | 61 | a |
如果你dump内存数据,在dump中也是从左到右读取的:
ADDRESS: ---------- MEMORY BYTES ---------- --- ASCII CHARACTERS --- 100: 00 61 62 63 64 00 00 00 00 00 ... .abcd....
因此,无论硬件实际采用什么字节顺序,单字节字符数据的字节顺序与大端字节顺序一致。
这样的字节顺序平台无关性不适于于多字节字符集,例如Unicode–每一个字符需要多于一个字符来表示。正确地读取多字节字符集,你必须知道存储它们的字节顺序。例如,如果你读错了两字节UTF-16字符 0x0041 (“A”) ,将其旋转为0x4100,你将得到一个UTF-16的中国象形文字,代表“灾难、灾难、邪恶或不幸”。 小心!
字节顺序之间的转换
你将可以看到,一个充满小端顺序的四字节整数的文件 (或UTF-16字符),在一个大端的机器上将会读取错误,反之亦然。
将多字节标量数据从一个机器传给另一个机器可能需要每一个标量单独地字节交换–每个标量的字节的顺序需要倒转这样数据才能被其他的硬件正确地读取。注意这样做需要十分了解输入的标量数据的大小 (UTF-16 字符) .你不能简单地交换所有的四字节序列,因为并不是所有的字节归结为四字节整数!
如果不知道两台机器的字节顺序的前提下能否写一个程序将其中一台上的标量数据传到另一台机器?答案是否定的,你必须知道两台机器的字节顺序。
一些程序试图使用一些技巧将所有标量数据转换成ASCII字符串,这样就可以实现字节顺序平台无关,比如要发送两字节整数0x010A (十进制266),程序传递三字节的ASCII字符 “266”, 因为ASCII字符串不依赖于字节顺序。远程机器会将ASCII转换回本地整型形式。
常见的一些文件格式与它们的字节顺序如下:
- Adobe Photoshop — Big Endian
- BMP (Windows and OS/2 Bitmaps) — Little Endian
- DXF (AutoCad) — Variable
- GIF — Little Endian
- IMG (GEM Raster) — Big Endian
- JPEG — Big Endian
- FLI (Autodesk Animator) — Little Endian
- MacPaint — Big Endian
- PCX (PC Paintbrush) — Little Endian
- PostScript — Not Applicable (text!)
- POV (Persistence of Vision ray-tracer) — Not Applicable (text!)
- QTM (Quicktime Movies) — Little Endian (on a Mac!)
- Microsoft RIFF (.WAV & .AVI) — Both
- Microsoft RTF (Rich Text Format) — Little Endian
- SGI (Silicon Graphics) — Big Endian
- Sun Raster — Big Endian
- TGA (Targa) — Little Endian
- TIFF — Both, Endian identifier encoded into file
- WPG (WordPerfect Graphics Metafile) — Big Endian (on a PC!)
- XWD (X Window Dump) — Both, Endian identifier encoded into file
转换成本地顺序
多字节整型转换为另一种格式非常简单,一个单一的函数被用来将其中一种转换成另一种。一个简单但不是非常高效的版本如下:
Function Reverse (N:LongInt) : LongInt ; Var B0, B1, B2, B3 : Byte ; Begin B0 := N Mod 256 ; N := N Div 256 ; B1 := N Mod 256 ; N := N Div 256 ; B2 := N Mod 256 ; N := N Div 256 ; B3 := N Mod 256 ; Reverse := (((B0 * 256 + B1) * 256 + B2) * 256 + B3) ; End ;
一个更加高效的版本依赖于十六进制数,使用AND, OR与NOT的屏蔽字操作, 和移位操作–SHL与SHR,代码类似下面的形式:
Function Reverse (N:LongInt) : LongInt ; Var B0, B1, B2, B3 : Byte ; Begin B0 := (N AND $000000FF) SHR 0 ; B1 := (N AND $0000FF00) SHR 8 ; B2 := (N AND $00FF0000) SHR 16 ; B3 := (N AND $FF000000) SHR 24 ; Reverse := (B0 SHL 24) OR (B1 SHL 16) OR (B2 SHL 8) OR (B3 SHL 0) ; End ;
当然还存在更加高效的代码,其中一些相当依赖机器与平台,选择其中工作最好的方法
Comments