值類型 引用類型 堆棧 堆 之 異想


看了很多值類型 和 引用類型的文章(谷歌能搜索出來的)
看了越多疑問越大,而這些資料中沒有具體的說明。
問題:
1、堆棧 和 堆 分別存於計算機的哪個硬件(CPU緩存,內存,硬盤)?
2、為什么只有值類型和引用類型?
3、為什么說引用類型是指針?
4、堆棧必堆小小多少?
 
以下是個人的分析,不是權威結果。
1、堆棧 和 堆 分別存於計算機的哪個硬件(CPU緩存,內存,硬盤)?
使用排除法來看這個問題
    (1)CPU緩存
    (2)內存
    (3)硬盤
        
    (3)可以排除堆棧的可能,因為 硬盤最慢
    (2)最有可能存堆棧,因為 速度適中,且相對來說存儲空間足夠大
    (1)可能性很小,應為僅幾年來CPU的緩存越來越大 但目前家用級別的CPU的1,2,3級緩存很少超過10MB(多核情況下每個核心分到的更少);
       
    真像 可能就是堆棧和堆都是放在內存里的。
    
    那么為什么堆棧比堆快呢?
    個人認為這情況和hashtable與list等數據容器的差異,差不多。
    存取方式決定的。
    堆棧:只能存取值類型,且先進先出,不夠的時候直接壓棧(就像"向右看起"的命令一樣) --簡單快捷
    堆:首先,堆的分配模式會存在碎片,並不是連續性的(這里直的是多個對象,找到一個適合的內存空間就把對象放進去,就像家居擺放物件一樣,有時候不貼個紙條的話,得找半天)。
    
2、為什么只有值類型和引用類型?
       這個我覺得追溯到本源比較好解釋,就是CPU只能進行數學計算。(看下匯編代碼會好理解些)
        值類型:就是數字,CPU可以直接進行運算。
        引用類型:最終指向值類型的指針。object是指針,object的ToString的函數還是一個指針,ToString內有String類型還是指針,最終指向一個Char[] 字符集合
       (注,我對String的理解就是Char[])。
        所以對象無法直接進行運算,只能通過指針找到能運算的部分,再進行運算。這也就是為啥只有2個類型了,一個是值用於運算,一個是指針,指向需要運算的地方。
 
3、為什么說引用類型是指針?
    由上可知,引用類型是指針必然性。
    一個Class內除了Int32等 值類型外其他皆是指針,委托,函數,函數內的對象,屬性,事件 都是指針。
    根據這種特性,指針(引用類型)作為參數傳遞,出來的時候會根據函數內的改變而改變,而值要作為參數輸入並輸出的話就要ref了。
    (注: 個人發現 DateTime 作為參數時具有值類型的特征)
    
4、堆棧必堆小小多少?
     未知,希望有知道的朋友能給出測試方法或者結果
     我的推測是既然是在內存,必然沒有限制,除非人為的限制
     我使用線程測試內存上限時發現沒有具體的上限。我的是64位+8G內存的筆記本以下是測試結果:(線程內分別創建class和sturct)
    X86: 一個應用程序只能達到1300多一點的線程,再也上不去了,提交內存約1440k
           class:運行穩定。sturct:大約2分鍾 內存溢出
   X64:一個應用程序只能達到8000多的線程,提交內存約10000k(還能繼續)
           class:運行穩定。sturct:行穩定
    
 
最終 我的結論:在C#里class 和sturct 如果真的是一個分配在堆,一個分配在堆棧,那么堆棧和堆的空間大小沒有區別,只存在速度的區別
        
以下是測試代碼:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace ConsoleApplication1
{
class Program
{
static void Main( string[] args)
{
for (var i = 0; i < 10000; i ++)
{
Thread th = new Thread(() = >
{
abc a = new abc( 1);
});
th.Start();
}
Console.ReadKey();
}
}
struct abc
{
public abc(Int32 x)
{
ds = String.Empty;
Test();
}
String ds;
private void Test()
{
while ( true)
{
ds += "A";
Thread.Sleep( 1000);
}
}
}
class bc
{
public bc(Int32 x)
{
ds = String.Empty;
Test();
}
String ds;
private void Test()
{
while ( true)
{
ds += "A";
Thread.Sleep( 1000);
}
}
}
}
 
 
 
IL:

class 的

// 代碼大小 28 (0x1c)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void [mscorlib]System.Object::.ctor()
IL_0006: nop
IL_0007: nop
IL_0008: ldarg.0
IL_0009: ldsfld string [mscorlib]System.String::Empty
IL_000e: stfld string ConsoleApplication1.bc::ds
IL_0013: ldarg.0
IL_0014: call instance void ConsoleApplication1.bc::Test()
IL_0019: nop
IL_001a: nop
IL_001b: ret
} // end of method bc::.ctor
 
sturct 的
// 代碼大小 20 (0x14)
.maxstack 8
IL_0000: nop
IL_0001: ldarg.0
IL_0002: ldsfld string [mscorlib]System.String::Empty
IL_0007: stfld string ConsoleApplication1.abc::ds
IL_000c: ldarg.0
IL_000d: call instance void ConsoleApplication1.abc::Test()
IL_0012: nop
IL_0013: ret
} // end of method abc::.ctor
 
 
堆棧容量測試:
using System;
using System.Threading;
namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            String txt = System.IO.File.ReadAllText("demo.txt");//一個3.11MB的文本

                Thread th = new Thread(() =>
                {
                    abc a = new abc(1);
                    for (var i = 0; i < 1000; i++)
                    {
                        a.ds += txt;
                    }
                });
                th.Start();
            
            Console.ReadKey();
        }


    }

    struct abc
    {
        public abc(Int32 x)
        {
            ds = String.Empty;
        }
       public String ds;

    }
    
}

測試結果:X64 能 提交內存3000K以上

 


我的結論和想法是這樣的:

首先我是站在CPU的角度去思考的。

1、堆棧 堆 可能都是一樣的指針,他們本身只是數據容器。

2、他們的區別在於存取方式不一致導致的存取速度不一樣。

3、堆棧 和堆 沒有具體大小,除非人為設置,且很有可能由CLR或者編譯器動態選擇數據容器。(畢竟我只能看到IL,看不到先X86反編譯匯編)

4、值類型傳參實為傳值,引用類型傳參實為傳地址(指針),這也是堆棧和堆數據使用上的區別。CPU對堆棧的態度是拿來就用,對堆就是找到再用。




免責聲明!

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



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