WEB开发网
开发学院软件开发C语言 关于C#静态函数什么时候被调用的问题 阅读

关于C#静态函数什么时候被调用的问题

 2010-09-30 21:00:31 来源:WEB开发网   
核心提示: 看起来,刚才所说的“基类的静态构造函数先于类型的静态构造执行”的论断有些问题……那么实际情况呢?实际情况应该是--类的静态构造函数在类型第一次被引用的时候调用,关于C#静态函数什么时候被调用的问题(2),好了,本来说到这里实际上是可以打住了,因

看起来,刚才所说的“基类的静态构造函数先于类型的静态构造执行”的论断有些问题……

那么实际情况呢?实际情况应该是--类的静态构造函数在类型第一次被引用的时候调用。好了,本来说到这里实际上是可以打住了,不过我们既然有些CLR的源代码(sscli 2.0),那看看源码里面到底怎么回事吧。

由于我是使用Windbg调试的sscli代码,把调试命令以及其结果全部贴出来的话,需要介绍很多背景知识,打算另写几篇文章讲解,这里就把关键的命令以及其输出结果贴出来,等背景知识讲完了以后,再把整个分析过程描述一下(工程量好像还是蛮大的)。

第一我可以负责任地说,针对demo.cs里面的这两段代码:

25.         B b = new B();
26.         A a = new A();

CLR的确是先即时编译( JIT)B的静态构造函数,然后再即时编译A的静态构造函数。

第二触发A的静态构造函数的编译是由执行B的静态构造函数的过程所引起的。

第三A 的静态构造函数即时编译完成以后,立即就被调用了,虽然这个时候B的静态构造函数只被执行了一半。

下面是static B()被最后JIT成的汇编代码,为了各位网友阅读方便,我在关键的地方加了一些注释描述代码的意思。

# 04e4fe60是static B最后被 JIT的代码在内存存放的地址,通过查看MethodDesc获取到。
# 至于如何获取MethodDesc以及MethodDesc是什么,呃……属于背景知识。
0:000> !u 04e4fe60
Normal JIT generated code
B..cctor()
Begin 04e4fe60, size 65
# 由于我设置参数COMPlus_JitHalt的值为.cctor,即通知CLR在JIT完成任何一个类型的静
# 态构造函数后,立即中断托管程序(当然还有CLR本身)的执行。
#
# 所以CLR在JIT完成以后,特意加上了一个断点。
>>> 04e4fe60 cc              int     3
# 下面的几段代码都是所有函数通用的初始化代码,比如需要多大的堆栈空间之类的。看
# 不懂没关系,如果想看懂的话,需要理解堆栈内存空间分配的方式,以及x86汇编的一# 些指令的意义。
04e4fe61 55              push    ebp
04e4fe62 8bec            mov     ebp,esp
04e4fe64 56              push    esi
04e4fe65 33f6            xor     esi,esi
04e4fe67 56              push    esi
04e4fe68 51              push    ecx
04e4fe69 52              push    edx
04e4fe6a b901000000      mov     ecx,1
04e4fe6f 6a00            push    0
04e4fe71 e2fc            loop    04e4fe6f
# 下面的代码判断是否要进行Just My Code方面的支持,Just My Code的实现原理我
# 还不是特别清楚,因此,呃……我们可以跳过……呃,还是跳过吧。
04e4fe73 b8d42b3c00      mov     eax,3C2BD4h
04e4fe78 8b00            mov     eax,dword ptr [eax]
04e4fe7a 85c0            test    eax,eax
04e4fe7c 0f8407000000    je      04e4fe89
04e4fe82 b860ad4579      mov     eax,offset mscorwks!JIT_DbgIsJustMyCode (7945ad60)
04e4fe87 ffd0            call    eax
04e4fe89 90              nop
04e4fe8a fc              cld
04e4fe8b 90              nop
04e4fe8c 90              nop
# 32D11FC是字符串“ssss”的内存地址,因为“ssss”是常量,所以它的地址总是在程序
# 启动的时候就已经分配好了,当然啦,如何看这个地址的内存,后面会讲到。
# 下面几段话就是把“ssss”的内存地址放到栈上,这里的栈,我指的是CLR是栈式机的
# 栈,因为后续的操作,也就是下面这条C#语句
# ------------------------------------------------------------------------------------------------------------------------
#                                          strText = “ssss”
# 会转化成下面这几条IL语句
#                                          ldstr “ssss”
#                                          stsfld string A::strText
#------------------------------------------------------------------------------------------------------------------------
# ldstr指令在执行之前,要求”ssss”的地址已经是在栈顶的。下面这段汇编指令就是干这
# 件事情的。
04e4fe8d b8fc112d03      mov     eax,32D11FCh
04e4fe92 8b00            mov     eax,dword ptr [eax]
# 3C2F54是A的MethodTable地址,什么是MethodTable以及它是干什么用的,也是
#背景知识。这篇文章不讲—否则就太长了,现在只要知道,这段汇编指令是为了让
# mscorwks!JIT_InitClass这个函数找到static A所对应的MethodDesc而做的操作。
04e4fe94 b9542f3c00      mov     ecx,3C2F54h
04e4fe99 50              push    eax
# 我们的学习 C++的时候,几乎 所有的书籍都会说,C++ 实例的构造函数是在实例的内存
# 被分配成功以后,才会调用 C++实例的构造函数。即下面的C++代码 :
#     TestClass *ptc = new TestClass;
# 如果从C语言的角度来看,实际上是分两步完成的:
#
# TestClass *ptc = (TestClass *)malloc(sizeof(TestClass));
# ptc->TestClass();   // 调用构造函数
#
# 这样做是因为避免构造函数初始化实例成员的时候,发生访问违规的情况(因为实例
# 的内存已经预先分配好了)。
#
# 说了这么多,实际上就是为了说明C#的静态构造函数的调用顺序也是这样的,一个类型
# 的静态变量在进程内存中是只能有一份备份,但是内存还是要被分配的。所以上面的
# 两个汇编指令将分配的内存地址,传给下面的mscorwks!JIT_InitClass函数。
#
# 为什么是mscorwks!JIT_InitClass,而不是static A这个函数呢?
# 因为静态构造函数只能被调用一次, 这个函数的作用就是:
#
# 1. 先看静态构造函数是否已经JIT过,如果没有,则即时编译这个静态构造函数。
# 2. 然后判断这个静态构造函数是否已经被调用过了,这个判断是通过读写一个标志位做#      到的。
# 3. 如果没有被调用过,则调用它
04e4fe9a b880e04479      mov     eax,offset mscorwks!JIT_InitClass (7944e080)
04e4fe9f ffd0            call    eax
# 32D0D3C就是CLR单独保存类型A的静态成员所分配的内存空间,当然啦,我现在说你
# 肯定不相信,一会我会演示查看这段内存的方法。
#
# 注意,32D0D3C是A的静态成员地址,不是B的,因为上面的C#语句
# ------------------------------------------------------------------------------------------------------------------------
#                                          strText = “ssss”
 
# 完整的写,应该是 A.strText = “ssss”;
#
04e4fea1 b83c0d2d03      mov     eax,32D0D3Ch
04e4fea6 50              push    eax
# 调用一个通用的函数STSFLD_REF_helper,将A.strText的值赋为”ssss”
04e4fea7 b850bfa179      mov     eax,offset mscorejt!STSFLD_REF_helper (79a1bf50)
04e4feac ffd0            call    eax
# 下面的指令是标准的函数退出前的清理操作。
04e4feae 90              nop
04e4feaf 54              push    esp
04e4feb0 55              push    ebp
04e4feb1 b814000000      mov     eax,14h
04e4feb6 50              push    eax
04e4feb7 b8e091a179      mov     eax,offset mscorejt!check_stack (79a191e0)
04e4febc ffd0            call    eax
04e4febe 8b75fc          mov     esi,dword ptr [ebp-4]
04e4fec1 8be5            mov     esp,ebp
04e4fec3 5d              pop     ebp
04e4fec4 c3              ret

上一页  1 2 3 4  下一页

Tags:关于 静态 函数

编辑录入:爽爽 [复制链接] [打 印]
赞助商链接