zoukankan      html  css  js  c++  java
  • (C#)if (this == null)?你在逗我,this 怎么可能为 null!用 IL 编译和反编译看穿一切

    if (this == null) Console.WriteLine("this is null"); 这句话一写,大家一定觉得荒谬,然而 if 内代码的执行却是可能的!本文讲介绍到底发生了什么。


     

    制造一个 this 可以为 null 的程序

    请看代码,这是我们的库函数:

    namespace Walterlv.Demo
    {
        public class Foo
        {
            public void Test()
            {
                if (this == null) Console.WriteLine("this is null");
                else Console.WriteLine("this is not null");
            }
        }
    }

    外面是这样调用的:

    namespace Walterlv.Demo
    {
        public class Program
        {
            private static void Main()
            {
                Foo p = null;
                p.Test();
            }
        }
    }

    这代码写出来,当然毫不犹豫地说——这会发生 NullReferenceException

    然而……

    现在我们改一改 Program 的 IL:

    Foo.Test

    将关注重点放在图中红框标注的部分,那是调用 p.Test 的地方。

    现在,我们将它从 callvirt 修改成 call

    第一步:反编译 exe 成 IL:

    
    # ildasm 在 C:Program Files (x86)Microsoft SDKsWindowsv10.0AinNETFX 4.7.1 Toolsx64 路径下
    
    ildasm /out=D:Desktopwdemo.il D:DesktopWalterlv.DemowdemoinDebugwdemo.exe

    第二步:修改 IL,将 callvirt 修改成 call

    IL_0004:  call   instance void Walterlv.Demo.Foo::Test()

    第三步:重新编译 IL 成 exe

    
    # ilasm 在 C:WindowsMicrosoft.NETFramework64v4.0.30319 路径下
    
    lvyi> ilasm /out:D:Desktopwdemo.exe D:Desktopwdemo.il
    
    Microsoft (R) .NET Framework IL Assembler.  Version 4.7.2556.0
    Copyright (c) Microsoft Corporation.  All rights reserved.
    Assembling 'D:Desktopwdemo.il'  to EXE --> 'D:Desktopwdemo.exe'
    Source file is ANSI
    
    Assembled method Walterlv.Demo.Program::Main
    Assembled method Walterlv.Demo.Program::.ctor
    Assembled method Walterlv.Demo.Foo::Test
    Assembled method Walterlv.Demo.Foo::.ctor
    Creating PE file
    
    Emitting classes:
    Class 1:        Walterlv.Demo.Program
    Class 2:        Walterlv.Demo.Foo
    
    Emitting fields and methods:
    Global
    Class 1 Methods: 2;
    Class 2 Methods: 2;
    Resolving local member refs: 1 -> 1 defs, 0 refs, 0 unresolved
    
    Emitting events and properties:
    Global
    Class 1
    Class 2
    Resolving local member refs: 0 -> 0 defs, 0 refs, 0 unresolved
    Writing PE file
    Operation completed successfully

    结果,现在再执行程序时,输出是 this is null

    this is null

    为什么此时 this 是 null

    从名字上看,call 是为了调用非虚方法、静态方法或者基类方法的;而 callvirt 是为了调用虚方法的。前者在编译时就将确认调用了某个类的某个方法,而后者将在运行时动态决定应该调用哪个。

    然而,当 IL 试图调用某个变量实例的一个方法时,由于不确定这个变量到底是不是实际的类型(还是基类型),所以都采用 callvirt 进行调用。call 在编译时就已确定调用,所以也没有加入 null 的判断;callvirt 却需要,因为通常都是实例使用。

    于是,此次便出现了 null.Test() 这样诡异的调用。

    一些建议和总结

    虽然我们制造出了一个 this 可能为 null 的情况,即便库和调用方是分开开发的,但实际开发中其实并不需要考虑这样的问题。


    参考资料

  • 相关阅读:
    POJ 1141 Brackets Sequence (区间DP)
    UVaLive 6585 && Gym 100299F Draughts (暴力+回溯)
    UVaLive 6950 && Gym 100299K Digraphs (DFS找环或者是找最长链)
    UVaLive 6588 && Gym 100299I (贪心+构造)
    UVa 1611 Crane (构造+贪心)
    Gym 100299C && UVaLive 6582 Magical GCD (暴力+数论)
    UVa 1642 Magical GCD (暴力+数论)
    UVaLive 6591 && Gym 100299L Bus (水题)
    UVaLive 6581 && Gym 100299B What does the fox say? (模拟+STL)
    HDU 5898 odd-even number (数位DP)
  • 原文地址:https://www.cnblogs.com/walterlv/p/10236476.html
Copyright © 2011-2022 走看看