当调用静态方法时,CLR如何管理?

4

我有一个静态类,其中包含如下所示的静态方法:

public static StaticTest
{
   public static void DoSomeWork()
   {
    /// Do Some work
   }
}

当调用DoSomeWork()函数时,CLR如何管理函数引用,因为静态类的实例显然无法创建?

在这种情况下,背后的机制是什么来调用函数?


如果你对如何调用静态函数感到困惑,你只需要使用你的class引用来调用它,因为static类无法实例化。就像这样StaticTest.DoSomeWork() - Dhrumil
我知道如何调用静态函数。但是我对CLR机制管理静态函数感到困惑。根据我的理解,当调用非静态函数时,首先必须创建类的实例。它们使用类的实例进行调用,并且该实例知道函数引用所在的位置并调用该函数。因此,我对它如何在静态类中调用静态函数的过程感到困惑。 - Tanzeel ur Rehman
2
你对方法的工作原理有错误的心理模型。一个方法只有一个“实例”,它被类的所有对象使用。CLR不区分静态方法和实例方法,它们只有一个属性,即方法的机器代码地址,最终是CALL指令的目标。实例方法与静态方法的特殊之处在于多了一个参数。你不需要在代码中编写这个额外的参数,但通常在方法内部使用它。这个参数就是this,也是使扩展方法起作用的机制。 - Hans Passant
4个回答

8
当CLR加载包含静态成员的程序集时,这些成员被放置在内存中的一个专用空间中,称为高频堆。高频堆中的对象永远不会被垃圾回收,以确保静态变量在应用程序的整个生命周期中可用。

谢谢,我理解你的回答是说所有静态变量和静态方法都被加载到高频堆中。那么为什么我们要使用className.functionName()来调用函数呢?难道我们不能直接访问它们吗,因为CLR知道高频堆的存在吗? - Tanzeel ur Rehman
类名只是一种方便地将这些方法分组的方式。从技术上讲,它并非必需的。 - Darin Dimitrov
1
事实上,不引用静态类名的常见用例是扩展方法。 - jdphenix
@DarinDimitrov和jdbphenix,谢谢。现在我明白了。你们能否推荐一本书或网址,让我能够理解CLR的内部和外部呢? - Tanzeel ur Rehman
@Tanzeel,这是MSDN上一个很好的参考链接:https://msdn.microsoft.com/zh-cn/magazine/cc163791.aspx - Darin Dimitrov
2
类名仅仅是一种方便分类方法的方式,从技术上讲并不是必需的。这只是一种过于简单化的说法。这种“分组”其实是有一个目的的。静态构造函数和字段初始化器与静态类“拥有”该方法相关联,再加上可见性规则(public/private)。 - xanatos

7

假设你有以下内容:

class Foo
{
    public void Bar()
    {
        // instance
    }

    public static void Fiz()
    {
        // instance
    }
}

而你需要做的是:

var temp = new Foo();
Foo.Fiz();
temp.Bar();

你的代码被翻译成了类似以下内容:

var temp = new Foo();
Foo.Fiz();
Foo.Bar(temp);

this是类的隐藏参数(第一个参数)。在Intel的C++中,这被称为thiscall调用约定。对于静态函数,根本就没有此参数。

如果您在代码上打开反汇编功能,您会看到它类似于:

            var temp = new Foo();
00007FFBD48042EC  lea         rcx,[7FFBD48563D8h]  
00007FFBD48042F3  call        00007FFC33E42400  
00007FFBD48042F8  mov         qword ptr [rsp+2B0h],rax  
00007FFBD4804300  mov         rax,qword ptr [rsp+2B0h]  
00007FFBD4804308  mov         qword ptr [rsp+2B8h],rax  
00007FFBD4804310  mov         rcx,qword ptr [rsp+2B8h]  
00007FFBD4804318  call        00007FFBD46ECA48  
00007FFBD480431D  mov         r11,qword ptr [rsp+2B8h]  
00007FFBD4804325  mov         qword ptr [rsp+30h],r11  
            Foo.Fiz();
00007FFBD480432A  call        00007FFBD46ECA40  
            temp.Bar();
00007FFBD480432F  mov         r11,qword ptr [rsp+30h]  
00007FFBD4804334  cmp         byte ptr [r11],0  
00007FFBD4804338  mov         rcx,qword ptr [rsp+30h]  
00007FFBD480433D  call        00007FFBD46ECA38  

正如您所看到的,Foo.Fiz 是一个直接的 call 00007FFBD46ECA40,而 temp.bar() 首先会检查 null (我认为是通过 mov + cmp 实现),然后将引用放入 rcx 并执行 call


3
刚要说的就是这个了。也许还有一个有用的信息,就是C#编译器会发出callvirt指令来检查实例方法接收者是否为空,并且对于静态方法则发出调用。但是,在IL中您也可以编写使用call并将空接收者传递给实例方法的代码。从某种意义上说,静态方法并不特殊,而是实例方法是静态方法的一种特殊类型。 - Mike Zboray

3
当一个方法开始执行时,CLR会识别该方法中使用的类型,并确保所引用的程序集已加载。
然后,它为方法中每个引用的类型创建“类型对象”(如果它们不存在)。
每个类型对象都包含类型对象指针、同步块索引、静态字段和一个方法表(其中包含该类型的每个方法的一个条目)。
public void DoSomething()
{
 InstanceClass objectInstance =  new InstanceClass();
 objectInstance.MethodName();
 StaticClass.MethodName();
 // i.e.here clr creates Type Objects for Instance class and StaticClass in the heap
}

enter image description here

当使用new运算符创建一个类的对象时,CLR会自动在该类的实例中创建一个类型对象指针,并将其初始化为引用相应的类型对象。
当调用实例(非虚拟)方法时,即objectInstance.MethodName(),CLR根据使用的变量类型识别类型对象,并在类型对象的方法表中查找方法,然后JIT该方法。
当调用静态方法时,即StaticTest.DoSomeWork(),CLR(JIT编译器)将根据类名识别静态类的“类型对象”,并在相应的类型对象的方法表中查找方法,然后JIT该方法。

基本上,实例方法需要创建并使用堆栈,但静态方法全部包含在堆上? - jamheadart

1
函数体存在于程序集中,是一组IL指令。当您调用该函数时,生成的IL代码如下:
call     void[assembly]StaticTest:DoSomeWork()

因此,运行时会在“程序集”中查找DoSomeWork的IL代码体,并进行JIT编译和执行。 从另一个角度来看,静态函数只是常规函数,但没有“this”引用可供使用。

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