朋友公司招聘用的一套C#基础面试题,10个码农8个错2个蒙,我也跳坑了…

Posted dotNET跨平台

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了朋友公司招聘用的一套C#基础面试题,10个码农8个错2个蒙,我也跳坑了…相关的知识,希望对你有一定的参考价值。

朋友公司的一套面试题,很有意思,参见如下代码:

class Program
    
        static void Main(string[] args)
        
            var t = Num();

            Console.WriteLine(t);
            Console.ReadLine();
        

        static int Num()
        
            int i = 10;

            try
            
                return i;
            
            finally
            
                i = 11;
                Console.WriteLine($"i=i");
            
        
    

请问这段代码会输出什么?相信有一点编程经验的朋友都知道,答案是:output: i=11 ,接下来的一个问题是这个 Num() 方法的返回值是多少, 10 还是 11 ?相信有很多朋友就有点迷糊了,那答案是多少呢?在不运行程序的情况下,我们从 IL汇编角度 来寻找答案。

一:IL 上寻找答案

要想看 IL,可以用 ILSpy 反编译一下。

.method private hidebysig static 
 int32 Num () cil managed 

 // Method begins at RVA 0x2074
 // Code size 39 (0x27)
 .maxstack 2
 .locals init (
  [0] int32 i,
  [1] int32
 )

 IL_0000: nop
 IL_0001: ldc.i4.s 10
 IL_0003: stloc.0
 .try
 
  IL_0004: nop
  IL_0005: ldloc.0
  IL_0006: stloc.1
  IL_0007: leave.s IL_0025
  // end .try
 finally
 
  IL_0009: nop
  IL_000a: ldc.i4.s 11
  IL_000c: stloc.0
  IL_000d: ldstr "i=0"
  IL_0012: ldloc.0
  IL_0013: box [mscorlib]System.Int32
  IL_0018: call string [mscorlib]System.String::Format(string, object)
  IL_001d: call void [mscorlib]System.Console::WriteLine(string)
  IL_0022: nop
  IL_0023: nop
  IL_0024: endfinally
  // end handler

 IL_0025: ldloc.1
 IL_0026: ret
 // end of method Program::Num

对比 return i 生成的 IL 代码,它的做法是将 i 保存到了一个临时变量 loc.1 处,最后将 loc.1 处的变量返回出去,我们很惊讶的发现 finally 块中并没有对 loc.1 处的变量赋值,也就是没有 stloc.1 指令,综合下来结果应该是 10, 而不是 11。

不知道可有朋友发现,这里的跳转指令 IL_0007: leave.s IL_0025 ,貌似直接跳过了 finally 块,那到底有没有执行呢?这个从 IL 上看不出,只能从 汇编角度 看啦。

二:查看汇编

接下来祭出windbg,汇编代码如下:

0:006> !U /d 008608a8
Normal JIT generated code
ConsoleApp1.Program.Num()
Begin 008608a8, size aa

D:\\net5\\ConsoleApp1\\ConsoleApp1\\Program.cs @ 23:
008608d4 c745e40a000000  mov     dword ptr [ebp-1Ch],0Ah

D:\\net5\\ConsoleApp1\\ConsoleApp1\\Program.cs @ 26:
008608db 90              nop

D:\\net5\\ConsoleApp1\\ConsoleApp1\\Program.cs @ 27:
008608dc 8b45e4          mov     eax,dword ptr [ebp-1Ch]
008608df 8945e0          mov     dword ptr [ebp-20h],eax
008608e2 90              nop
008608e3 c745ec00000000  mov     dword ptr [ebp-14h],0
008608ea c745f0fc000000  mov     dword ptr [ebp-10h],0FCh
008608f1 6849098600      push    860949h
008608f6 eb00            jmp     008608f8

D:\\net5\\ConsoleApp1\\ConsoleApp1\\Program.cs @ 30:
008608f8 90              nop

D:\\net5\\ConsoleApp1\\ConsoleApp1\\Program.cs @ 31:
008608f9 c745e40b000000  mov     dword ptr [ebp-1Ch],0Bh

D:\\net5\\ConsoleApp1\\ConsoleApp1\\Program.cs @ 32:
00860900 b9a8429778      mov     ecx,offset mscorlib_ni+0x142a8 (789742a8) (MT: System.Int32)
00860905 e8ea27daff      call    006030f4 (JitHelp: CORINFO_HELP_NEWSFAST)
0086090a 8945dc          mov     dword ptr [ebp-24h],eax
0086090d 8b0544235d03    mov     eax,dword ptr ds:[35D2344h] ("i=0")
00860913 8945d4          mov     dword ptr [ebp-2Ch],eax
00860916 8b45dc          mov     eax,dword ptr [ebp-24h]
00860919 8b55e4          mov     edx,dword ptr [ebp-1Ch]
0086091c 895004          mov     dword ptr [eax+4],edx
0086091f 8b45dc          mov     eax,dword ptr [ebp-24h]
00860922 8945d0          mov     dword ptr [ebp-30h],eax
00860925 8b4dd4          mov     ecx,dword ptr [ebp-2Ch]
00860928 8b55d0          mov     edx,dword ptr [ebp-30h]
0086092b e820d74c78      call    mscorlib_ni!System.String.Format(System.String, System.Object)$##6000545 (78d2e050)
00860930 8945d8          mov     dword ptr [ebp-28h],eax
00860933 8b4dd8          mov     ecx,dword ptr [ebp-28h]
00860936 e829465b78      call    mscorlib_ni!System.Console.WriteLine(System.String)$##6000B79 (78e14f64)
0086093b 90              nop

D:\\net5\\ConsoleApp1\\ConsoleApp1\\Program.cs @ 33:
0086093c 90              nop
0086093d 58              pop     eax
0086093e ffe0            jmp     eax

D:\\net5\\ConsoleApp1\\ConsoleApp1\\Program.cs @ 34:
00860940 8b45e0          mov     eax,dword ptr [ebp-20h]
00860943 8d65fc          lea     esp,[ebp-4]
00860946 5f              pop     edi
00860947 5d              pop     ebp
00860948 c3              ret

D:\\net5\\ConsoleApp1\\ConsoleApp1\\Program.cs @ 22:
00860949 c745f000000000  mov     dword ptr [ebp-10h],0
00860950 ebee            jmp     00860940

为了方便比对,我再把代码行数给截出来。

一般函数的返回值都是放在 eax 中,所以重点关注下 eax 的赋值部分,仔细观察它的路径大概就是下面四句代码:

mov     dword ptr [ebp-1Ch],0Ah
mov     eax,dword ptr [ebp-1Ch]
mov     dword ptr [ebp-20h],eax
mov     eax,dword ptr [ebp-20h]

也就是说,最后的 eax 还是当初的 0Ah= 10, 也和 IL 反映出来的一致,return 操作的底层会将返回值放到一个 临时区域 = ebp-20h 中,最后返回 临时区域 中的值。

再回到刚才 IL 部分的疑问,从上面的 jmp 008608f8 指令看,return 的下一步就直接进了 finally 块,最后执行 RET 弹出下一行代码指令到 EIP 中完成方法体的执行,大概就是这个样子。

以上是关于朋友公司招聘用的一套C#基础面试题,10个码农8个错2个蒙,我也跳坑了…的主要内容,如果未能解决你的问题,请参考以下文章

我想做个码农

前端面试题:Vue面试题及Vue源码解析分享

2020最新Java面试题集合,朋友圈疯传,知乎过万点赞

python面试题基础部分 80题

面试了8家软件公司测试岗位,面试题大盘点,我真的尽力了

2019_JAVA面试题_真实总结