在DEBUG写的.NET程序时,发现一个有趣的现象,就是本应该返回true
的BitConverter.IsLittleEndian
,返回的却是false
!但在使用BitConverter
之后,再次查看其值,却发现变成了正常的true
状态。难道在X86的系统上存在两种状态?
DEBUG的代码如下:
...
// 前面没有使用BitConverter
// DEBUG断点设置到这里的时候,可以看到是false的值
// BitConverter.IsLittleEndian
// 如果使用BitConverter之后,得到的值却是true
byte[] bdata = BitConverter.GetBytes(num);
// 此时DEBUG中看到的是true.
但这明显不科学,首先BitConverter.IsLittleEndian
在X86下应该是false
,其次,不应该在一个系统内,同时出现true
和false
这两种状态。
抱着好奇的心态,翻开了源代码看了一下,看到如下代码:
public static readonly bool IsLittleEndian = true;
赫然写着true
啊!哪里来的false
?
经过一番搜索,发现有这么一句:
通过调试器读取成员并不会触发执行成员的初始化代码。1
也就是说:通过调试器读取内存中的成员仅仅是读取到该对象的默认初始值而已。有这么一句话,解释起来就豁然开朗了。
为了验证这个说法的真实性,我们可以自己定义一个类来进行验证下:
public static class DebuggerInitTester
{
public static bool BoolData = true;
}
在DEBUG中我们可以看到,我们可以在没有调用前尝试获取到BoolData
值,发现是true
,并非是默认的false
!这是什么情况,再仔细看了一遍代码,发现BitConverter
里面有静态构造函数:
static BitConverter()
{
// Note: this type is marked as 'beforefieldinit'.
BitConverter.IsLittleEndian = true;
}
在BitConverter
中使用了静态的构造函数,而我们的并没有,看来问题在这里了,我们也改造下代码:
static DebuggerInitTester()
{
DebuggerInitTester.BoolData = true;
}
再次测试,确实,这个时候默认是false
了。看起来问题就在这个静态构造函数了,原来不是一直听说static
的是在加载前初始化了的么?看来并不是这个样子的,还是Too Young Too Simple!
看来是beforefieldinit引起的问题了(可参见Mitchell的另一篇关于BeforeFieldInit的文章)
如果没有BeforeFieldInit标记,则CLR会在第一次访问静态成员的时候调用静态构造函数对静态类中的字段进行初始化。比如,DebuggerInitTester
这个类,我们定义了静态构造函数,因此将初始化BoolData
的时机锁定了。
一切看起来是这样了,可再仔细看,又蒙了——压根就没有静态构造函数!BitConverter
那个构造函数是自动生成的,是有BeforeFieldInit标记的。
.class public abstract auto ansi sealed beforefieldinit System.BitConverter
extends System.Object
{
.custom instance void __DynamicallyInvokableAttribute::.ctor() = ( 01 00 00 00 )
}
根据BeforeFieldInit规则,也就是说BitConverter
中IsLittleEndian的初始化会在访问前的任意时候完成,自然也可能会和有自定义静态构造函数那样在第一次访问的时候初始化,也许CLR对于BitConverter是这样处理。
如果能解释初始化时间点后,调试器读取成员不触发成员初始化代码倒是非常可以理解的,因为这样调试器的调试才安全可靠,不至于导致意外的发生(如:读取导致数据变化带来后继逻辑的)。
困惑
BitConverter
由于使用了BeforeFieldInit标记,将初始化时机交给了CLR,也就是说这将导致IsLittleEndian
初始化时间点不确定,很有可能就是在第一次访问的时候进行初始化的。
令人疑惑的是,为什么自定义的DebuggerInitTester
类中的BoolData
会那么快就完成了初始化呢?另外测试了好几次(其他类)均是非常早的完成初始化。
这里面到底是什么原因决定CLR来选择BeforeFieldInit标记过的类的初始化时机?
参考
- Why does BitConverter.LittleEndian return false on my x86 machine?
- IsLittleEndian field reports false, but it must be Little-Endian?