CLR如何知道静态字段是否已经被初始化?

3

我一直在想静态字段 / 构造函数的问题。

静态类在第一次引用其字段时进行初始化,这很简单。

但是CLR如何知道这是第一次呢?

2个回答

4
CLR维护了一个表格,记录了已加载的所有类型及其初始化状态。如果A正在使用B的静态字段,则CLR知道A正在使用B,在初始化A时也会初始化B。因此,不会在每次访问时检查是否初始化依赖项。但是,它通过类型的依赖关系图确保。如果您对实现细节感兴趣,可以查看CoreCLRDomainLocalModuleIsClassInitialized方法以及创建类实例时的usage。希望这回答了您的问题。

是的,非常感谢。虽然我得承认,在访问字段时每次都这样检查似乎很昂贵(与C++相比)。 - Patrick Braunstorfer
1
需要明确的是,这个检查不会在每次访问类时执行。当使用类被加载时,将检查类加载状态。如果您的 A 类使用 B.foo 的静态属性,那么当使用 A 时,它的依赖关系将被检查。因此,当 A 被初始化时,B 也将被初始化。对 B.foo 的访问不会产生此类检查。 - Kolja
我对答案进行了一些编辑,以使得这个检查发生的时间更加清晰。 - Kolja

2

当一个静态类的字段被引用时,该类会在第一次初始化,这很容易理解。

但实际上情况并不那么简单。如果忽略方法调用,除非被标记为beforefieldinit,否则静态类必须在其字段首次访问之前进行初始化。如果被标记为beforefieldinit,则可以提前进行初始化。 (Jon Skeet在他的文章中有更多关于beforefieldinit的信息。)

这如何影响类初始化的检查?由于CLR使用JIT编译,因此它在JIT编译方法时检查类初始化。如果该类被标记为beforefieldinit且尚未初始化,则JIT编译器立即对其进行初始化。然后它实际上编译方法,在其中可以假设类已经被初始化,因此无需进行任何检查。

如果没有beforefieldinit,如果类尚未初始化,则JIT编译器必须发出代码以在每个潜在的第一次字段访问之前检查初始化。但是,如果类已经初始化并且正在JIT编译另一个方法,则JIT编译器不再需要在那里发出检查。

这可能会对性能产生负面影响。从上述内容可以清楚地看出,为了防止这种情况发生,您需要确保将有问题的类标记为beforefieldinit。而从C#实现这一点的方法是不要使用static构造函数,仅使用static字段初始化程序。


有趣。我猜如果潜在的第一个字段Access散布在整个解决方案中并且严重依赖于外部输入,那么这将是一个特别棘手的问题。非常感谢您提供深入的答案。 - Patrick Braunstorfer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接