下決心作好自媒體到如今有一個月了,關注個人兄弟應該知道我產出了很多文章,號裏的粉絲也多起來了,我也盡最大努力作到有問必回,如今是基礎的、高深的問題都接踵而來,可我也只是一隻小菜鳥,想飛也飛不動了(┬_┬),昨天號裏有位朋友被面試官問到可空類型的原理,回答的很差,面試官也是,面就面唄,又給不了多少銀子,還動不動就原理,哪有那麼多原理,搞得雙方都尷尬😄😄😄。面試
這種問題要怎麼挖呢? 我在以前的文章也聊過,C#代碼到機器碼中間有兩個編譯過程,一個是csc編譯後的IL代碼,一個是jit編譯後的native代碼,因此搞懂IL代碼和native代碼就是咱們要深究的方向,我仍是把那篇文章的圖拿過來。ide
爲了方便演示,我就定義一個int?
類型,接收非null和null兩種狀況。函數
static void Main(string[] args) { int? num1 = 10; int? num2 = null; Console.WriteLine("執行結束啦!"); Console.ReadLine(); }
挖IL代碼簡單,用ILSPY小工具就能夠了,編譯後生成的IL代碼以下:工具
.method private hidebysig static void Main ( string[] args ) cil managed { // Method begins at RVA 0x2048 // Code size 36 (0x24) .maxstack 2 .entrypoint .locals init ( [0] valuetype [mscorlib]System.Nullable`1<int32> num1, [1] valuetype [mscorlib]System.Nullable`1<int32> num2 ) IL_0000: nop IL_0001: ldloca.s 0 IL_0003: ldc.i4.s 10 IL_0005: call instance void valuetype [mscorlib]System.Nullable`1<int32>::.ctor(!0) IL_000a: ldloca.s 1 IL_000c: initobj valuetype [mscorlib]System.Nullable`1<int32> IL_0012: ldstr "執行結束啦!" IL_0017: call void [mscorlib]System.Console::WriteLine(string) IL_001c: nop IL_001d: call string [mscorlib]System.Console::ReadLine() IL_0022: pop IL_0023: ret } // end of method Program::Main
這IL代碼仍是很是易懂的,比彙編簡單多啦(┬_┬),能夠看到int ?
就是 System.Nullable<int32>
,而後從valuetype
標記能夠看到這玩意是個值類型,因此把上面的代碼迴轉成C#代碼就是下面這樣。佈局
{ static void Main(string[] args) { //int? num1 = 10; //int? num2 = null; Nullable<int> num3 = new Nullable<int>(10); Nullable<int> num4 = new Nullable<int>(); Console.WriteLine("執行結束啦!"); Console.ReadLine(); }
很簡單吧,那怎麼輸出num3和num4呢? 直接Console.WriteLine
就行了。this
這裏你確定有一個疑問,爲何num3輸出10,而num4什麼都沒輸出呢? 哈哈,這是由於Nullable的ToString()被重寫了,再來看下ToString被重寫成啥樣了,代碼以下:spa
public struct Nullable<T> where T : struct { private bool hasValue; internal T value; [NonVersionable] [__DynamicallyInvokable] public Nullable(T value) { this.value = value; hasValue = true; } [__DynamicallyInvokable] public override string ToString() { if (!hasValue) { return ""; } return value.ToString(); } }
能夠看到ToString方法裏要麼返回空字符串要麼返回你在構造函數中塞入的value,這這麼簡單,IL代碼挖到這裏就能夠了。線程
要看num1和num2的機器代碼,其實也就是看 Nullable<T>
的內存佈局方式,這裏我使用windbg,仍是使用 !clrstack -l
查看線程棧。code
int? num1 = 10; int? num2 =null; 0:007> ~0s ntdll!ZwReadFile+0x14: 00007ffc`ec11aa64 c3 ret 0:000> !clrstack -l OS Thread Id: 0x5364 (0) Child SP IP Call Site ConsoleApp4.Program.Main(System.String[]) [C:\dream\Csharp\ConsoleApp1\ConsoleApp4\Program.cs @ 21] LOCALS: 0x00000018a9dfeaf8 = 0x0000000a00000001 0x00000018a9dfeaf0 = 0x0000000000000000 00000018a9dfed08 00007ffcd5b66c93 [GCFrame: 00000018a9dfed08]
從LOCALS
中能夠看到,num1和num2的線程棧上存放的內容分別是0x0000000a00000001
和 0x0000000000000000
, 不過這值也挺奇怪的,一個是1一個是0。。。咱們用 dd 命令把地址轉儲出來。blog
0:000> dd 0x00000018a9dfeaf8 00000018`a9dfeaf8 00000001 0000000a a9dfec08 00000018 0:000> dd 0x00000018a9dfeaf0 00000018`a9dfeaf0 00000000 00000000 00000001 0000000a
在num1的內存區域中有一個十六進制值 0000000a
,這就是十進制的10,那前面的 00000001
是什麼東西呢? 你們不要忘啦, int?
是語法糖, 你如今看的是 Nullable<T>
哈。。。
看清楚啦,這個結構體裏面有兩個值類型字段,天然 00000001
就是 hasValue=true
啦。 num2
也就好理解了,兩個默認值也就是兩個0了。00000000 00000000
。
若是你的內存數據量特別大的話,你就要小心了,int? 比 int 在x64上要多佔4個字節,也就是多一倍,不管線程棧仍是託管堆。
確定有人比較疑惑,bool在C#中不就是一個字節嘛? 你怎麼說是4個字節呢? 你要是問我,我只能說從windbg上看就是這樣的,x64系統的線程棧上就是以4個字節爲一個單位,你不信的話,我就在代碼中定義不一樣字段的 值類型,你看看在線程棧上的分佈不就好啦,以事實說話。
byte b1 = byte.MaxValue; byte b2 = byte.MaxValue; short b3 = short.MaxValue; short b4 = short.MaxValue; int b5 = int.MaxValue; int b6 = int.MaxValue; 0:000> !clrstack -l OS Thread Id: 0xa98 (0) ConsoleApp4.Program.Main(System.String[]) [C:\dream\Csharp\ConsoleApp1\ConsoleApp4\Program.cs @ 25] LOCALS: 0x000000a8395fedbc = 0x00000000000000ff 0x000000a8395fedb8 = 0x00000000000000ff 0x000000a8395fedb4 = 0x0000000000007fff 0x000000a8395fedb0 = 0x0000000000007fff 0x000000a8395fedac = 0x000000007fffffff 0x000000a8395feda8 = 0x000000007fffffff
而後把最小的地址0x000000a8395feda8
轉儲出來。
0:000> dd 0x000000a8395feda8 000000a8`395feda8 7fffffff 7fffffff 00007fff 00007fff 000000a8`395fedb8 000000ff 000000ff 395feec8 000000a8 000000a8`395fedc8 395fefc8 000000a8 395fee00 000000a8 000000a8`395fedd8 d5b66c93 00007ffc 98e72d30 000001ee 000000a8`395fede8 76504140 00007ffc 00000000 00000000 000000a8`395fedf8 00000000 00007ffc 395feef0 000000a8 000000a8`395fee08 971d0b20 000001ee 00000000 00000000 000000a8`395fee18 d5b66b79 00007ffc 00000000 00000000
對比一下能夠看到上面的 7fffffff, 00007fff,000000ff
就是相應的int,short,byte
的MaxValue, 都是佔用4個字節的空間,沒問題吧。
var arr1 = new int[] { 10 }; var arr2 = new int?[] { 14 }; 0:000> !clrstack -l OS Thread Id: 0x23f8 (0) 000000859a1fec60 00007ffc76630967 ConsoleApp4.Program.Main(System.String[]) [C:\dream\Csharp\ConsoleApp1\ConsoleApp4\Program.cs @ 32] LOCALS: 0x000000859a1feca0 = 0x000002773cb32d70 0x000000859a1fec98 = 0x000002773cb32d90 000000859a1feeb8 00007ffcd5b66c93 [GCFrame: 000000859a1feeb8] 0:000> !do 0x000002773cb32d70 Name: System.Int32[] MethodTable: 00007ffcd2d58538 EEClass: 00007ffcd2ec5918 Size: 28(0x1c) bytes Array: Rank 1, Number of elements 1, Type Int32 (Print Array) Fields: None 0:000> !do 0x000002773cb32d90 Name: System.Nullable`1[[System.Int32, mscorlib]][] MethodTable: 00007ffcd3fb2058 EEClass: 00007ffcd30221a0 Size: 32(0x20) bytes Array: Rank 1, Number of elements 1, Type VALUETYPE (Print Array) Fields: None 0:000> !objsize 0x000002773cb32d70 sizeof(000002773cb32d70) = 32 (0x20) bytes (System.Int32[]) 0:000> !objsize 0x000002773cb32d90 sizeof(000002773cb32d90) = 32 (0x20) bytes (System.Nullable`1[[System.Int32, mscorlib]][])
能夠看到,一個是28byte,一個是32byte,多出來的就是那個hasValue哈,有一點要注意了,用!objsize
打出來都是32byte,這是由於28byte
要靠8對齊就變成32byte
啦, 而後我把兩個值類型轉儲出來,以下圖:
挖到這裏,不知道可挖到了面試官的盲區啦😄,總之int?就是 Nullable<T>
, 並且可空比非可空多4個字節的空間,最後你們要看本身狀況使用啦。