专栏首页walterlv - 吕毅的博客(C#)if (this == null)?你在逗我,this 怎么可能为 null!用 IL 编译和反编译看穿一切

(C#)if (this == null)?你在逗我,this 怎么可能为 null!用 IL 编译和反编译看穿一切

(C#)if (this == null)?你在逗我,this 怎么可能为 null!用 IL 编译和反编译看穿一切

发布于 2018-03-31 00:26 更新于 2018-09-01 00:12

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:

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

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

第一步:反编译 exe 成 IL:

# ildasm 在 C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.7.1 Tools\x64 路径下 ildasm /out=D:\Desktop\wdemo.il D:\Desktop\Walterlv.Demo\wdemo\bin\Debug\wdemo.exe

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

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

第三步:重新编译 IL 成 exe

# ilasm 在 C:\Windows\Microsoft.NET\Framework64\v4.0.30319 路径下 lvyi> ilasm /out:D:\Desktop\wdemo.exe D:\Desktop\wdemo.il Microsoft (R) .NET Framework IL Assembler. Version 4.7.2556.0 Copyright (c) Microsoft Corporation. All rights reserved. Assembling 'D:\Desktop\wdemo.il' to EXE --> 'D:\Desktop\wdemo.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 是 null

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

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

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

一些建议和总结

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


参考资料

本文会经常更新,请阅读原文: https://walterlv.com/post/this-could-be-null.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。

本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名 吕毅 (包含链接: https://walterlv.com ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系 (walter.lv@qq.com)

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 为什么不应该公开用来同步的加锁对象?为什么不应该 lock(this)/lock(string) 或者 lock 任何非私有对象?

    如果你编写线程安全代码时为了省事儿直接 lock(this),或者早已听说不应该 lock(this),只是不知道原因,那么阅读本文可以帮助你了解原因...

    walterlv
  • 用 AppContext 解决类库的更新兼容问题

    2017-09-30 15:45

    walterlv
  • 使用 Emit 生成 IL 代码

    发布于 2018-04-22 13:14 更新于 2018-09...

    walterlv
  • tf.random_uniform()

    从均匀分布中输出随机值。生成的值在该 [minval, maxval) 范围内遵循均匀分布.下限 minval 包含在范围内,而上限 maxval 被排除在外。...

    于小勇
  • ReactNative之从“拉皮条”来看RN中的Spring动画

    上篇博客我们聊了RN中关于Timing的动画,详情请参见于《ReactNative之结合具体示例来看RN中的的Timing动画》本篇博客我们将从一个“拉皮条”的...

    lizelu
  • python 实现两个npy档案合并

    需求:把一个文件夹下的多个csv文件合并成一个文件,文件的格式是相同的,只是按照不同的月份分成了多个文件,现将文件夹下的文件进行合并

    砸漏
  • 智享交流会|犀牛鸟精英计划全视角体验职业生涯,激发更多成长智慧

    导语:2019年11月30日,腾讯犀牛鸟智享交流会在北京和深圳两地顺利开展。智享交流会作为犀牛鸟精英人才培养计划为入选学生搭建的线下成长交流平台之一,旨在通过...

    腾讯高校合作
  • 刚刚,5G商用牌照正式发布,一共四张!

    2019年6月6日,据央视报道,工信部向中国电信、中国移动、中国联通、中国广电发放第五代移动通信业务牌照(即5G商用牌照)。此举标志中国电信产业正式进入5G时代...

    用户2769421
  • 刚刚,5G商用牌照正式发布,一共四张

    2019年6月6日,据央视报道,工信部向中国电信、中国移动、中国联通、中国广电发放第五代移动通信业务牌照(即5G商用牌照)。此举标志中国电信产业正式进入5G时代...

    大数据文摘
  • macOS 应用注入开发简介与实践

    本文主要介绍的是动态链接库注入的相关实践,这不仅仅局限于macOS,同时对iOS应用也具有相同的效果,希望大家可以借鉴使用.

    代码行者

扫码关注云+社区

领取腾讯云代金券