代码如下,问在release版本中,函数get_Length执行几次?(凸老头,显摆还不给答案)
static int get_Length(string s)
{
return s.Length;
}
static void Test(string s)
{
for (var i = 0; i < get_Length(s); i++)
{
Console.WriteLine(s[i]);
}
}
static void Main()
{
var s = new String('a', 10);
Test(s);
Console.Read();
}
解析:在VS里设置断点察看,get_Length函数察看是11次,但是,这到底对不对呢? 我们要透过现象看本质,废话少说,上工具-〉Windbg
首先,我们先在windbg里面为get_Length设置断点。
〉.load sos
〉!bpmd Learn_Test Learn.Test.Program.get_Length
断点添加成功。
执行程序,〉 g
程序执行结束。。。咦,为什么断点处没有执行,有猫腻,继续分析......
加载jit
〉sxe ld:mscorjit
〉g
Test函数处设置断点
〉!bpmd Learn_Test Learn.Test.Program.Test
执行到断点处
〉g
察看Test函数信息
〉!name2ee Learn_Test Learn.Test.Program.Test
察看Test函数jit CODE
〉!u 000007ff00180170
0:000> !u 000007ff00180170
Normal JIT generated code
Learn.Test.Program.Test(System.String)
Begin 000007ff00180170, size 3f
>>> 000007ff`00180170 53 push rbx
000007ff`00180171 56 push rsi
000007ff`00180172 57 push rdi
000007ff`00180173 4883ec20 sub rsp,20h
000007ff`00180177 488bf9 mov rdi,rcx
000007ff`0018017a 33db xor ebx,ebx
000007ff`0018017c 8b770c mov esi,dword ptr [rdi+0Ch]
000007ff`0018017f 90 nop
//-----------------------------------------------------------------------------------------------------
000007ff`00180180 3bde cmp ebx,esi
000007ff`00180182 7d1c jge 000007ff`001801a0
000007ff`00180184 8b470c mov eax,dword ptr [rdi+0Ch]
000007ff`00180187 3bd8 cmp ebx,eax
000007ff`00180189 731e jae 000007ff`001801a9
000007ff`0018018b 4863cb movsxd rcx,ebx
000007ff`0018018e 668b4c4f10 mov cx,word ptr [rdi+rcx*2+10h]
000007ff`00180193 e8980867f8 call mscorlib_ni+0x930a30 (000007fe`f87f0a30) (System.Console.WriteLine(Char), mdToken: 060007c0)
000007ff`00180198 83c301 add ebx,1
000007ff`0018019b ebe3 jmp 000007ff`00180180
000007ff`0018019d 0f1f00 nop dword ptr [rax]
//-----------------------------------------------------------------------------------------------------
000007ff`001801a0 4883c420 add rsp,20h
000007ff`001801a4 5f pop rdi
000007ff`001801a5 5e pop rsi
000007ff`001801a6 5b pop rbx
000007ff`001801a7 f3c3 rep ret
000007ff`001801a9 e8b2a14df9 call mscorwks!JIT_RngChkFail (000007fe`f965a360)
000007ff`001801ae 90 nop
需要注意的是中间那段,可以看出,ebx相当于for循环里的i,字符串的长度取自 [rdi+0Ch],并没有调用get_Lengt函数。
我们可以来看一下.net中字符串的存储情况,在Main函数设置断点,察看寄存器得到s的地址指针。
0:000> !do 23dc740
Name: System.String
MethodTable: 000007fef882ec90
EEClass: 000007fef843b038
Size: 46(0x2e) bytes
(C:\Windows\assembly\GAC_64\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
String: aaaaaaaaaa
Fields:
MT Field Offset Type VT Attr Value Name
000007fef8835f00 4000096 8 System.Int32 1 instance 11 m_arrayLength
000007fef8835f00 4000097 c System.Int32 1 instance 10 m_stringLength
000007fef88306d8 4000098 10 System.Char 1 instance 61 m_firstChar
000007fef882ec90 4000099 20 System.String 0 shared static Empty
>> Domain:Value 000000000028cad0:00000000023d1308 <<
000007fef8830588 400009a 28 System.Char[] 0 shared static WhitespaceChars
>> Domain:Value 000000000028cad0:00000000023d1a80 <<
在这里可以看到,offset c 处就是字符串的length, 在32位机器上应该是offset 8
.NET CLR STRING 内存结构:
【4字节方法表指针(64位机器为8字节)】【4字节附加字段】【长度】【字符串内容】
对于这个 get_Length 方法,编译器肯定试图做inline 处理,接着二次优化会发现目标具有固定性,因为在 .net 中 string 是不可修改的。因此 main 中 string.ctor 后已经直接返回了字符串的指针,那么直接将 eax + c 就可以获得字符串长度,将这个作为 "参数" 提交给 test 方法就可以了。因此 JIT 根本不打算编译 get_Length,也无需做内联处理。(技术支持:雨老大)
浙公网安备 33010602011771号