看了很多值類型 和 引用類型的文章(谷歌能搜索出來的)
看了越多疑問越大,而這些資料中沒有具體的說明。
問題:
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);
}
}
}
}
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對堆棧的態度是拿來就用,對堆就是找到再用。