zoukankan      html  css  js  c++  java
  • 关于for循环中变量定义的位置

    问题

    最近跟同事讨论for循环中变量定义在哪里的问题。先看一段代码:

            private void ForInner()
            {
    
                for (int i = 0; i < 5; i++)
                {
                    var obj = new MyClass();
                    Console.WriteLine(obj.name);
                }
            }
    这是我们正常习惯写的代码。同事的意思是说如果照上面那样写因为每循环一次,obj的变量就要在堆栈上分配一段空间,造成浪费。应该把obj的定义拿到for代码块的外面这样可以少分配一些内存提高效率,代码如下:
         private void ForOuter()
            {
    
                MyClass obj;
                for (int i = 0; i < 5; i++)
                {
                    obj = new MyClass();
                    Console.WriteLine(obj.name);
                }
    
            }

    从正常的角度上来看这样写变量obj确实比上面要少分配内存,因为obj只是定义了一次,只在堆栈上分配了一次内存,用来保存指向MyClass的实例的地址。理解这个问题首先得对.net的内存分配有个了解。简单科普一下:

    一个引用类型的对象被创建分为以下几步

    1. MyClass obj ; 在线程堆栈上创建一个obj的变量,用来保存实例对象的地址。

    2. new MyClass();在托管堆上创建 MyClass的实例对象。

    3. “=”操作符号 obj存储实例对象的地址。

    参考资料:

    Anytao的大作:http://www.cnblogs.com/anytao/archive/2007/12/07/must_net_19.html

    好了,有了这个背景知识,就不难理解同事为什么说第二种写法会少分配内存了。对于第一种写法会创建多次变量obj,第二种只有一次。那么事实上是不是如此呢?

    答案

    要查明这个问题我们只需要借助IL,看一下这2段代码的IL就一清二楚了。

    image

    看2段IL的代码,我们很容易就发现,其实不管是哪种写法,生成的IL几乎是一样的,不同的只是locals init初始化变量的顺序先后的差异。对于第一种写法IL并没有在循环体内去每次都声明obj变量。所以这两种写法在本质上是一样的。但是本人还是推荐第一种写法,在循环体里直接定义变量。因为循环体里实例化的对象,一般都是循环完成就不在使用了可以被回收,或者被其他业务对象引用,如放入某个List里面去。但是第二种写法的obj变量必定还保持着最后一次循环所创建的对象。这个对象的释放会被限制,且后面的新人接手你的代码时容易误操作了这个变量,造成不必要的bug。

    疑惑

    经过这次对IL的查看,还发现一个问题,难道在IL中方法的局部变量都是在方法体最上部全部初始化好了吗,于是我又做了测试:

     private void ForMany()
            {
    
                int z = 1;
    
                var a = new MyClass();
                var b = new MyClass();
                var c = new MyClass();
                var d = new MyClass();
                var e = new MyClass();
                var f = new MyClass();
                var g = new MyClass();
                if( z==1)
                    return;
                var h = new MyClass();
                var i = new MyClass();
                var j = new MyClass();
                var k = new MyClass();
                var l = new MyClass();
                var n = new MyClass();
                    return;
            }

    我在方法里定义了很多的变量。看看IL是否全部一次初始化好。结果如下:

    image

    不出所料,IL在一开始就把所有的变量都初始化好了。这样我就想不通了,如果代码的中间就有条件语句控制return呢,后面的变量不一定都会用到,完全可以不去初始化,这样难得不会浪费内存空间吗?还是说我对.locas init的理解有误,望解惑!

    解惑

    @钧梓昊逑
     方法内部的临时变量是在进入方法时就在栈上分配的,通过栈顶指针的移动实现变量分配与回收,效率是极高的,对于你说的内存浪费,的确会有,这也是为什么推荐写小方法的原因。

     我想着应该是最好的答案了~

  • 相关阅读:
    个人总结05
    微软拼音的用户体验
    个人总结04
    典型用户和用户场景模式
    个人总结03
    个人总结02
    构建之法阅读笔记06
    个人总结01
    学习进度条——第七周
    WebApi学习总结系列第五篇(消息处理管道)
  • 原文地址:https://www.cnblogs.com/kklldog/p/3013534.html
Copyright © 2011-2022 走看看