關於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的理解有誤,望解惑!

解惑

@鈞梓昊逑
  方法內部的臨時變量是在進入方法時就在棧上分配的,通過棧頂指針的移動實現變量分配與回收,效率是極高的,對於你說的內存浪費,的確會有,這也是為什么推薦寫小方法的原因。

 我想着應該是最好的答案了~

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM