Main 和 静态构造函数 到底谁先执行?

Posted dotNET跨平台

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Main 和 静态构造函数 到底谁先执行?相关的知识,希望对你有一定的参考价值。

最近被问到一个很有意思的问题,到底是 Main函数 先执行还是 静态构造函数 先执行?参考如下代码:

class Program
    
        static Program()
        
            Console.WriteLine("我是 静态构造 函数!");
        

        static void Main(string[] args)
        
            Console.WriteLine("我是 main 方法!");
        
    

想要知道答案很简单,把代码跑起来哈 😄😄😄。

从输出结果看,静态函数先执行,这就很困惑了,一直都被教育,执行流应该是 非托管层 -> 托管层 -> Main入口 这条思路,咋就先执行 静态构造函数 了呢?

一:寻找答案

如果你会玩 windbg 并且能看懂一些汇编,那就非常简单了,可以通过 windbg 对 Main 函数下一个断点。

0:000> !bpmd Program.cs:14
0:000> g
JITTED ConsoleApp3!ConsoleApp3.Program.Main(System.String[])
Setting breakpoint: bp 04C40450 [ConsoleApp3.Program.Main(System.String[])]
Breakpoint 0 hit
ChangeEngineState
eax=04c40450 ebx=0057f354 ecx=02669e7c edx=7897aa50 esi=0057f300 edi=001940b0
eip=04c40450 esp=0057f2ec ebp=0057f2f4 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
04c40450 55              push    ebp
0:000> !U 04c40450
Normal JIT generated code
ConsoleApp3.Program.Main(System.String[])
ilAddr is 04DD205E pImport is 035F9978
Begin 04C40450, size 36

D:\\net5\\ConsoleApp4\\ConsoleApp3\\Program.cs @ 14:
>>> 04c40450 55              push    ebp
04c40451 8bec            mov     ebp,esp
04c40453 50              push    eax
04c40454 894dfc          mov     dword ptr [ebp-4],ecx
04c40457 b9e0d9b004      mov     ecx,4B0D9E0h
04c4045c ba01000000      mov     edx,1
04c40461 e8fe2eb1fb      call    00753364 (JitHelp: CORINFO_HELP_GETSHARED_NONGCSTATIC_BASE)
04c40466 833d38d9b00400  cmp     dword ptr ds:[4B0D938h],0
04c4046d 7405            je      04c40474
04c4046f e87cfcf473      call    coreclr!JIT_DbgIsJustMyCode (78b900f0)
04c40474 90              nop

D:\\net5\\ConsoleApp4\\ConsoleApp3\\Program.cs @ 15:
04c40475 8b0d68206603    mov     ecx,dword ptr ds:[3662068h] ("")
04c4047b e8d0fcffff      call    04c40150 (System.Console.WriteLine(System.String), mdToken: 06000081)
04c40480 90              nop

D:\\net5\\ConsoleApp4\\ConsoleApp3\\Program.cs @ 16:
04c40481 90              nop
04c40482 8be5            mov     esp,ebp
04c40484 5d              pop     ebp
04c40485 c3              ret

从输出信息看,一直被教育的 非托管层 -> 托管层 -> Main入口 这条链路是没有问题的,因为在执行 Console.WriteLine 之前有一条 call 00753364 (JitHelp: CORINFO_HELP_GETSHARED_NONGCSTATIC_BASE) 指令,接下来我们看看这条指令是干嘛的?

可以用 uf 00753364 反编译下这个方法,简化后的代码如下:

0:000> uf 00753364
00753364 89c8            mov     eax,ecx
00753366 f644101801      test    byte ptr [eax+edx+18h],1
0075336b 7401            je      0075336e  Branch

0075336d c3              ret  Branch

0075336e 55              push    ebp
0075336f 89e5            mov     ebp,esp
00753371 52              push    edx
00753372 e869c22f78      call    coreclr!JIT_GetSharedNonGCStaticBase_Portable (78a4f5e0)
00753377 5a              pop     edx
00753378 5d              pop     ebp
00753379 c3              ret

0:000> uf 78a4f5e0
coreclr!JIT_GetSharedNonGCStaticBase_Portable [d:\\a\\_work\\1\\s\\src\\vm\\jithelpers.cpp @ 1340]:
 1340 78a4f5e0 f644111801      test    byte ptr [ecx+edx+18h],1
 1346 78a4f5e5 0f8403000000    je      coreclr!JIT_GetSharedNonGCStaticBase_Helper (78a4f5ee)  Branch

coreclr!JIT_GetSharedNonGCStaticBase_Portable+0xb [d:\\a\\_work\\1\\s\\src\\vm\\jithelpers.cpp @ 1348]:
 1348 78a4f5eb 8bc1            mov     eax,ecx
 1355 78a4f5ed c3              ret

coreclr!JIT_GetSharedNonGCStaticBase_Helper [d:\\a\\_work\\1\\s\\src\\vm\\jithelpers.cpp @ 1399]:
 1399 78a4f5ee 6a5c            push    5Ch
 1399 78a4f5f0 b8aa9da978      mov     eax,offset coreclr!Debugger::GenericHijackFunc+0x1116a (78a99daa)
 1399 78a4f5f5 e87b110200      call    coreclr!_EH_prolog3_catch (78a70775)
 1399 78a4f5fa 8bfa            mov     edi,edx
 1399 78a4f5fc 8bf1            mov     esi,ecx
 1399 78a4f5fe 33db            xor     ebx,ebx
 ...

从输出信息看,代码流程大概是: CORINFO_HELP_GETSHARED_NONGCSTATIC_BASE -> JIT_GetSharedNonGCStaticBase_Portable -> JIT_GetSharedNonGCStaticBase_Helper -> ..., 调用链还是蛮深的,我就不往下追了,不过可以从名字上看,大概就是和 初始化静态变量 有关,那如何更好的验证呢?很简单,我们可以在 静态构造函数 上埋一个断点,然后查看调用链,看看上面有没有 JIT_GetSharedNonGCStaticBase_Helper 方法即可。

0:000> !bpmd Program.cs:9
0:000> g
JITTED ConsoleApp3!ConsoleApp3.Program..cctor()
Setting breakpoint: bp 04C50498 [ConsoleApp3.Program..cctor()]
Breakpoint 0 hit
ChangeEngineState
eax=04c50498 ebx=0057e848 ecx=0250ebb0 edx=0250ebb0 esi=0057e7c0 edi=00000000
eip=04c50498 esp=0057e7ac ebp=0057e7b4 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
04c50498 55              push    ebp
0:000> k
 # ChildEBP RetAddr      
WARNING: Frame IP not in any known module. Following frames may be wrong.
00 0057e7b4 78a6a0ef     0x4c50498
01 0057e7b4 78a433bf     coreclr!CallDescrWorkerInternal+0x34 [D:\\a\\_work\\1\\s\\src\\vm\\i386\\asmhelpers.asm @ 615] 
02 0057e7e0 78a74422     coreclr!CallDescrWorkerWithHandler+0x62 [d:\\a\\_work\\1\\s\\src\\vm\\callhelpers.cpp @ 72] 
03 0057e824 789a6e3b     coreclr!DispatchCallDebuggerWrapper+0x25 [d:\\a\\_work\\1\\s\\src\\vm\\callhelpers.cpp @ 163] 
04 0057e874 789a6d70     coreclr!DispatchCallSimple+0x63 [d:\\a\\_work\\1\\s\\src\\vm\\callhelpers.cpp @ 230] 
05 0057e8e8 789a5bed     coreclr!MethodTable::RunClassInitEx+0xb4 [d:\\a\\_work\\1\\s\\src\\vm\\methodtable.cpp @ 3377] 
06 0057f230 789a593e     coreclr!MethodTable::DoRunClassInitThrowing+0x249 [d:\\a\\_work\\1\\s\\src\\vm\\methodtable.cpp @ 3607] 
07 0057f250 78a4f648     coreclr!MethodTable::CheckRunClassInitThrowing+0xb9 [d:\\a\\_work\\1\\s\\src\\vm\\methodtable.cpp @ 3762] 
08 0057f2d0 007e3377     coreclr!JIT_GetSharedNonGCStaticBase_Helper+0x5a [d:\\a\\_work\\1\\s\\src\\vm\\jithelpers.cpp @ 1410] 
09 0057f2dc 04c50466     0x7e3377
0a 0057f2e8 78a6a0ef     0x4c50466
0b 0057f2f4 789eff91     coreclr!CallDescrWorkerInternal+0x34 [D:\\a\\_work\\1\\s\\src\\vm\\i386\\asmhelpers.asm @ 615] 
0c (Inline) --------     coreclr!CallDescrWorkerWithHandler+0x55 [d:\\a\\_work\\1\\s\\src\\vm\\callhelpers.cpp @ 70] 
0d 0057f380 78a2c895     coreclr!MethodDescCallSite::CallTargetWorker+0x167 [d:\\a\\_work\\1\\s\\src\\vm\\callhelpers.cpp @ 604] 
0e (Inline) --------     coreclr!MethodDescCallSite::Call+0x11 [d:\\a\\_work\\1\\s\\src\\vm\\callhelpers.h @ 468] 
0f 0057f4fc 78a2e292     coreclr!RunMain+0x1e4 [d:\\a\\_work\\1\\s\\src\\vm\\assembly.cpp @ 1558] 
...

上面的 08 号栈帧就是调用链上的 coreclr!JIT_GetSharedNonGCStaticBase_Helper 方法,那就说明找对了, 00 号栈帧就是当前的静态构造函数。

二:总结

综合上面的信息可知,你所看到的 静态构造函数 先执行是一种假象,只不过是在 Main 函数中先做了一个 call 调用而已,本质上还是 Main 在先,cctor 在后,

以上是关于Main 和 静态构造函数 到底谁先执行?的主要内容,如果未能解决你的问题,请参考以下文章

(Java)类实例化过程中,父类和子类的静态变量静态代码块成员变量构造函数的执行顺序是什么?

java中静态代码块构造代码块构造方法main函数的执行顺序?

静态代码块-普通代码块-构造代码块(好多图系列)

静态代码块构造代码块构造函数

java 静态main继承 的执行顺序

Unity 静态类执行Update()