前言
我在“浅谈 ZipInteger”一文中的 ZipInteger 结构中使用了 BitConverter 类的 GetBytes 方法。当时我是假设 GetBytes 方法根据 IsLittleEndian 的值不同而按照不同的顺序返回字节数组。但是 MSDN 有关 BitConverter 类的文档中没有对此作出明确的说明。请参见我在 MSDN 论坛的一个贴子“请问 BitConverter.GetBytes 方法以什么顺序返回字节数组”:
在 MSDN 文档的“BigInteger 构造函数 (Byte[])”中提到: value 数组中的各个字节应该为 little-endian 顺序,从最低序位字节到最高序位字节。 将数值转换为字节数组的大多数方法,例如 BigInteger.ToByteArray 和 BitConverter.GetBytes,以 little-endian 顺序返回字节数组。 按照我的理解,该 MSDN 文档认为 BitConverter.GetBytes 方法总是以 little-endian 顺序返回字节数组,而不管 BitConverter.IsLittleEndian 的值如何。 而在 BitConverter 类的文档中并没有明确指出 BitConverter.GetBytes 方法应该以什么顺序返回字节数组。 不知我的理解是否正确。
该贴子并没有得到靠谱的答复。
测试程序
那么,我们写个程序来测试一下吧。下面就是 BitConverterTester.cs:
using System; namespace Skyiv.Tester { static class BitConverterTester { static void Main() { Console.WriteLine(" OS Version: " + Environment.OSVersion); Console.WriteLine(" CLR Version: " + Environment.Version); Console.WriteLine(" IsLittleEndian: " + BitConverter.IsLittleEndian); long n = 0x1234567890ABCDEF; double d = 1; Console.WriteLine(n.ToString("X") + ": " + BitConverter.ToString(BitConverter.GetBytes(n))); Console.WriteLine(d.ToString("F14") + ": " + BitConverter.ToString(BitConverter.GetBytes(d))); } } }
这个程序在 Windows Server 2003 操作系统的 .NET Framework 4 环境下编译和运行:
C:\CS\BitConverterTester> csc BitConverterTester.cs Microsoft(R) Visual C# 2010 编译器 4.0.30319.1 版 版权所有(C) Microsoft Corporation。保留所有权利。 C:\CS\BitConverterTester> BitConverterTester OS Version: Microsoft Windows NT 5.2.3790 Service Pack 2 CLR Version: 4.0.30319.1 IsLittleEndian: True 1234567890ABCDEF: EF-CD-AB-90-78-56-34-12 1.00000000000000: 00-00-00-00-00-00-F0-3F C:\CS\BitConverterTester>
在 Ubuntu 10.10 操作系统的 Mono 2.8.2 环境下编译和运行:
ben@ben-m4000t:~/work/BitConverterTester$ dmcs BitConverterTester.cs ben@ben-m4000t:~/work/BitConverterTester$ mono28 BitConverterTester.exe OS Version: Unix 2.6.35.24 CLR Version: 4.0.30319.1 IsLittleEndian: True 1234567890ABCDEF: EF-CD-AB-90-78-56-34-12 1.00000000000000: 00-00-00-00-00-00-F0-3F ben@ben-m4000t:~/work/BitConverterTester$
这两次运行的结果都在预料之中,BitConverter 类的 GetBytes 方法以 Little-Endian 顺序返回字节数组。但是,在这两次运行中,IsLittleEndian 的值都为 True,所以还是没有解决我们的问题。
查看 Microsoft .NET Framework 4 中相关的源程序代码
祭出 Reflector 这个神器:
在 mscorlib.dll 的 System 命名空间下找到 BitConverter 类:
如上图如示,IsLittleEndian 是 BitConverter 类的静态只读字段。
如上图所示,在 BitConverter 类的静态构造函数中,直接把 IsLittleEndian 这个静态只读字段的值赋值为 true。由于我没有 Microsoft 实现 BitConverter 类的 C# 源程序代码,不知道是 Microsoft 的 C# 源程序中就是直接这样写呢,还是实际上是有根据平台来判断的,但是 C# 编译器在具体平台上优化了这段代码。
如上图所示,GetBytes(Int64) 方法也非常简单,直接通过不安全的指针转换就得到了相应的字节数组。学过 C 语言中的朋友想必非常熟悉这种做法。这下清楚了,BitConverter 类的 GetBytes 方法是根据 IsLittleEndian 的值的不同按照不同顺序来返回字节数组的。MSDN 文档在“BigInteger 构造函数 (Byte[]) ”中的相关说法:“例如 BigInteger.ToByteArray 和 BitConverter.GetBytes,以 little-endian 顺序返回字节数组”是不正确的。
继续看下去:
上图中的 ToInt64 方法也明确地根据 IsLittleEndian 的值采取不同的动作。其实,既然在静态构造函数中明确地给 IsLittleEndian 赋值为 true,这里也可以省略对 IsLittleEndian 的判断,直接按照 IsLittleEndian 的值为 true 去做就行了,可以稍微节省点代码,提高点速度。
我们看到,GetBytes(Double) 方法也非常简单。
查看 Mono 2.8.2 中相关的源代码
我们可以到 http://ftp.novell.com/pub/mono/sources/mono/ 下载 Mono 的源代码,然后按以下方法找到 BitConverter 类的源程序代码:
ben@ben-m4000t:~$ cd src/mono-2.8.2 ben@ben-m4000t:~/src/mono-2.8.2$ find . -name BitConverter.cs ./mcs/class/corlib/System/BitConverter.cs ben@ben-m4000t:~/src/mono-2.8.2$
下面就是 BitConverter.cs:
// // System.BitConverter.cs // // Author: // Matt Kimball (matt@kimball.net) // // // Copyright (C) 2004 Novell, Inc (http://www.novell.com) // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // using System.Text; namespace System { public static class BitConverter { static readonly bool SwappedWordsInDouble = DoubleWordsAreSwapped (); public static readonly bool IsLittleEndian = AmILittleEndian (); static unsafe bool AmILittleEndian () { // binary representations of 1.0: // big endian: 3f f0 00 00 00 00 00 00 // little endian: 00 00 00 00 00 00 f0 3f // arm fpa little endian: 00 00 f0 3f 00 00 00 00 double d = 1.0; byte *b = (byte*)&d; return (b [0] == 0); } static unsafe bool DoubleWordsAreSwapped () { // binary representations of 1.0: // big endian: 3f f0 00 00 00 00 00 00 // little endian: 00 00 00 00 00 00 f0 3f // arm fpa little endian: 00 00 f0 3f 00 00 00 00 double d = 1.0; byte *b = (byte*)&d; return b [2] == 0xf0; } public static long DoubleToInt64Bits (double value) { return ToInt64 (GetBytes (value), 0); } public static double Int64BitsToDouble (long value) { return ToDouble (GetBytes (value), 0); } internal static double InternalInt64BitsToDouble (long value) { return SwappableToDouble (GetBytes (value), 0); } unsafe static byte[] GetBytes (byte *ptr, int count) { byte [] ret = new byte [count]; for (int i = 0; i < count; i++) { ret [i] = ptr [i]; } return ret; } unsafe public static byte[] GetBytes (bool value) { return GetBytes ((byte *) &value, 1); } unsafe public static byte[] GetBytes (char value) { return GetBytes ((byte *) &value, 2); } unsafe public static byte[] GetBytes (short value) { return GetBytes ((byte *) &value, 2); } unsafe public static byte[] GetBytes (int value) { return GetBytes ((byte *) &value, 4); } unsafe public static byte[] GetBytes (long value) { return GetBytes ((byte *) &value, 8); } [CLSCompliant (false)] unsafe public static byte[] GetBytes (ushort value) { return GetBytes ((byte *) &value, 2); } [CLSCompliant (false)] unsafe public static byte[] GetBytes (uint value) { return GetBytes ((byte *) &value, 4); } [CLSCompliant (false)] unsafe public static byte[] GetBytes (ulong value) { return GetBytes ((byte *) &value, 8); } unsafe public static byte[] GetBytes (float value) { return GetBytes ((byte *) &value, 4); } unsafe public static byte[] GetBytes (double value) { if (SwappedWordsInDouble) { byte[] data = new byte [8]; byte *p = (byte*)&value; data [0] = p [4]; data [1] = p [5]; data [2] = p [6]; data [3] = p [7]; data [4] = p [0]; data [5] = p [1]; data [6] = p [2]; data [7] = p [3]; return data; } else { return GetBytes ((byte *) &value, 8); } } unsafe static void PutBytes (byte *dst, byte[] src, int start_index, int count) { if (src == null) throw new ArgumentNullException ("value"); if (start_index < 0 || (start_index > src.Length - 1)) throw new ArgumentOutOfRangeException ("startIndex", "Index was" + " out of range. Must be non-negative and less than the" + " size of the collection."); // avoid integer overflow (with large pos/neg start_index values) if (src.Length - count < start_index) throw new ArgumentException ("Destination array is not long" + " enough to copy all the items in the collection." + " Check array index and length."); for (int i = 0; i < count; i++) dst[i] = src[i + start_index]; } unsafe public static bool ToBoolean (byte[] value, int startIndex) { if (value == null) throw new ArgumentNullException ("value"); if (startIndex < 0 || (startIndex > value.Length - 1)) throw new ArgumentOutOfRangeException ("startIndex", "Index was" + " out of range. Must be non-negative and less than the" + " size of the collection."); if (value [startIndex] != 0) return true; return false; } unsafe public static char ToChar (byte[] value, int startIndex) { char ret; PutBytes ((byte *) &ret, value, startIndex, 2); return ret; } unsafe public static short ToInt16 (byte[] value, int startIndex) { short ret; PutBytes ((byte *) &ret, value, startIndex, 2); return ret; } unsafe public static int ToInt32 (byte[] value, int startIndex) { int ret; PutBytes ((byte *) &ret, value, startIndex, 4); return ret; } unsafe public static long ToInt64 (byte[] value, int startIndex) { long ret; PutBytes ((byte *) &ret, value, startIndex, 8); return ret; } [CLSCompliant (false)] unsafe public static ushort ToUInt16 (byte[] value, int startIndex) { ushort ret; PutBytes ((byte *) &ret, value, startIndex, 2); return ret; } [CLSCompliant (false)] unsafe public static uint ToUInt32 (byte[] value, int startIndex) { uint ret; PutBytes ((byte *) &ret, value, startIndex, 4); return ret; } [CLSCompliant (false)] unsafe public static ulong ToUInt64 (byte[] value, int startIndex) { ulong ret; PutBytes ((byte *) &ret, value, startIndex, 8); return ret; } unsafe public static float ToSingle (byte[] value, int startIndex) { float ret; PutBytes ((byte *) &ret, value, startIndex, 4); return ret; } unsafe public static double ToDouble (byte[] value, int startIndex) { double ret; if (SwappedWordsInDouble) { byte* p = (byte*)&ret; if (value == null) throw new ArgumentNullException ("value"); if (startIndex < 0 || (startIndex > value.Length - 1)) throw new ArgumentOutOfRangeException ("startIndex", "Index was" + " out of range. Must be non-negative and less than the" + " size of the collection."); // avoid integer overflow (with large pos/neg start_index values) if (value.Length - 8 < startIndex) throw new ArgumentException ("Destination array is not long" + " enough to copy all the items in the collection." + " Check array index and length."); p [0] = value [startIndex + 4]; p [1] = value [startIndex + 5]; p [2] = value [startIndex + 6]; p [3] = value [startIndex + 7]; p [4] = value [startIndex + 0]; p [5] = value [startIndex + 1]; p [6] = value [startIndex + 2]; p [7] = value [startIndex + 3]; return ret; } PutBytes ((byte *) &ret, value, startIndex, 8); return ret; } unsafe internal static double SwappableToDouble (byte[] value, int startIndex) { double ret; if (SwappedWordsInDouble) { byte* p = (byte*)&ret; if (value == null) throw new ArgumentNullException ("value"); if (startIndex < 0 || (startIndex > value.Length - 1)) throw new ArgumentOutOfRangeException ("startIndex", "Index was" + " out of range. Must be non-negative and less than the" + " size of the collection."); // avoid integer overflow (with large pos/neg start_index values) if (value.Length - 8 < startIndex) throw new ArgumentException ("Destination array is not long" + " enough to copy all the items in the collection." + " Check array index and length."); p [0] = value [startIndex + 4]; p [1] = value [startIndex + 5]; p [2] = value [startIndex + 6]; p [3] = value [startIndex + 7]; p [4] = value [startIndex + 0]; p [5] = value [startIndex + 1]; p [6] = value [startIndex + 2]; p [7] = value [startIndex + 3]; return ret; } else if (!IsLittleEndian) { byte* p = (byte*)&ret; if (value == null) throw new ArgumentNullException ("value"); if (startIndex < 0 || (startIndex > value.Length - 1)) throw new ArgumentOutOfRangeException ("startIndex", "Index was" + " out of range. Must be non-negative and less than the" + " size of the collection."); // avoid integer overflow (with large pos/neg start_index values) if (value.Length - 8 < startIndex) throw new ArgumentException ("Destination array is not long" + " enough to copy all the items in the collection." + " Check array index and length."); p [0] = value [startIndex + 7]; p [1] = value [startIndex + 6]; p [2] = value [startIndex + 5]; p [3] = value [startIndex + 4]; p [4] = value [startIndex + 3]; p [5] = value [startIndex + 2]; p [6] = value [startIndex + 1]; p [7] = value [startIndex + 0]; return ret; } PutBytes ((byte *) &ret, value, startIndex, 8); return ret; } public static string ToString (byte[] value) { if (value == null) throw new ArgumentNullException ("value"); return ToString (value, 0, value.Length); } public static string ToString (byte[] value, int startIndex) { if (value == null) throw new ArgumentNullException ("value"); return ToString (value, startIndex, value.Length - startIndex); } public static string ToString (byte[] value, int startIndex, int length) { if (value == null) throw new ArgumentNullException ("byteArray"); // The 4th and last clause (start_index >= value.Length) // was added as a small fix to a very obscure bug. // It makes a small difference when start_index is // outside the range and length==0. if (startIndex < 0 || startIndex >= value.Length) { // special (but valid) case (e.g. new byte [0]) if ((startIndex == 0) && (value.Length == 0)) return String.Empty; throw new ArgumentOutOfRangeException ("startIndex", "Index was" + " out of range. Must be non-negative and less than the" + " size of the collection."); } if (length < 0) throw new ArgumentOutOfRangeException ("length", "Value must be positive."); // note: re-ordered to avoid possible integer overflow if (startIndex > value.Length - length) throw new ArgumentException ("startIndex + length > value.Length"); if (length == 0) return string.Empty; StringBuilder builder = new StringBuilder(length * 3 - 1); int end = startIndex + length; for (int i = startIndex; i < end; i++) { if (i > startIndex) builder.Append('-'); char high = (char)((value[i] >> 4) & 0x0f); char low = (char)(value[i] & 0x0f); if (high < 10) high += '0'; else { high -= (char) 10; high += 'A'; } if (low < 10) low += '0'; else { low -= (char) 10; low += 'A'; } builder.Append(high); builder.Append(low); } return builder.ToString (); } } }
可以看出,在第 39 行通过第 41 到 50 行的 AmILittleEndian 方法给 IsLittleEndian 这个静态只读字段赋值。这个 AmILittleEndian 方法中的注释写得很明白,其实有三种不台的平台,除了 Big-Endian 和 Little-Endian 外,还有一种“arm fpa little endian”,在这里也归于 Little-Endian。但是在后面的方法中是要以不同的手段进行处理的。
另外,不象 Microsoft .NET Framework 4,Mono 中 BitConverter 类的各种重载的公有静态GetBytes 方法是统一调用第 78 到 87 行的私有静态 GetBytes 方法来实现其功能的。
从 Mono 中相关的源程序代码中也可以看出,BitConverter 类的 GetBytes 方法是根据 IsLittleEndian 的值的不同以不同的顺序返回字节数组的。
结论
总结一下,MSDN 文档在“BigInteger 构造函数 (Byte[]) ”中的相关说法:“例如 BigInteger.ToByteArray 和 BitConverter.GetBytes,以 little-endian 顺序返回字节数组”是不正确的。BitConverter 类的 GetBytes 方法是根据 IsLittleEndian 的值的不同以不同的顺序返回字节数组的。