自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

用 Span 對(duì) C# 進(jìn)程中三大內(nèi)存區(qū)域進(jìn)行統(tǒng)一訪問 ,太厲害了!

存儲(chǔ) 存儲(chǔ)軟件 后端
總的來說,這一篇主要是從思想上帶大家一起認(rèn)識(shí) Span,以及如何用 Span 對(duì)接 三大區(qū)域內(nèi)存,關(guān)于 Span 的好處以及源碼解析。

[[348727]]

 一:背景

1. 講故事

前段時(shí)間寫了幾篇 C# 漫文,評(píng)論留言中有很多朋友多次提到 Span,周末抽空看了下,確實(shí)是一個(gè)非常的新結(jié)構(gòu),讓我想到了當(dāng)年的WCF,它統(tǒng)一了.NET下各種零散的分布式技術(shù),包括:.NET Remoteing,WebService,NamedPipe,MSMQ,而這里的 Span 統(tǒng)一了 C# 進(jìn)程中的三大塊內(nèi)存訪問,包括:棧內(nèi)存, 托管堆內(nèi)存, 非托管堆內(nèi)存,畫個(gè)圖如下:

 

接下來就和大家具體聊聊這三大塊的內(nèi)存統(tǒng)一訪問。

二:進(jìn)程中的三大塊內(nèi)存解析

1. 棧內(nèi)存

大家應(yīng)該知道方法內(nèi)的局部變量是存放在棧上的,而且每一個(gè)線程默認(rèn)會(huì)被分配 1M 的內(nèi)存空間,我舉個(gè)例子:

  1. static void Main(string[] args) 
  2.         { 
  3.             int i = 10; 
  4.             long j = 20; 
  5.             List<string> list = new List<string>(); 
  6.         } 

上面 i,j 的值都是存于棧上,list的堆上內(nèi)存地址也是存于棧上,為了看個(gè)究竟,可以用 windbg 驗(yàn)證一下:

  1. 0:000> !clrstack -l 
  2. OS Thread Id: 0x2708 (0) 
  3.         Child SP               IP Call Site 
  4. 00000072E47CE558 00007ff89cf7c184 [InlinedCallFrame: 00000072e47ce558] Interop+Kernel32.ReadFile(IntPtr, Byte*, Int32, Int32 ByRef, IntPtr) 
  5. 00000072E47CE558 00007ff7c7c03fd8 [InlinedCallFrame: 00000072e47ce558] Interop+Kernel32.ReadFile(IntPtr, Byte*, Int32, Int32 ByRef, IntPtr) 
  6. 00000072E47CE520 00007FF7C7C03FD8 ILStubClass.IL_STUB_PInvoke(IntPtr, Byte*, Int32, Int32 ByRef, IntPtr) 
  7. 00000072E47CE7B0 00007FF8541E530D System.Console.ReadLine() 
  8. 00000072E47CE7E0 00007FF7C7C0101E DataStruct.Program.Main(System.String[]) [E:\net5\ConsoleApp2\ConsoleApp1\Program.cs @ 22] 
  9.     LOCALS: 
  10.         0x00000072E47CE82C = 0x000000000000000a 
  11.         0x00000072E47CE820 = 0x0000000000000014 
  12.         0x00000072E47CE818 = 0x0000018015aeab10 

通過 clrstack -l 查看線程棧,最后三行可以明顯的看到 0a -> 10, 14 -> 20 , 0xxxxxxb10 => list堆地址,除了這些簡(jiǎn)單類型,還可以在棧上分配復(fù)雜類型,這里就要用到 stackalloc 關(guān)鍵詞, 如下代碼:

  1. int* ptr = stackalloc int[3] { 10, 11, 12 }; 

問題就在這里,指針類型雖然靈活,但是做任何事情都比較繁瑣,比如說:

  • 查找某一個(gè)數(shù)是否在 int[] 中
  • 反轉(zhuǎn) int[]
  • 剔除尾部的某一個(gè)數(shù)字(比如 12)

就拿第一個(gè)問題來說,操作指針的代碼如下:

  1. //指針接收 
  2.             int* ptr = stackalloc int[3] { 10, 11, 12 }; 
  3.  
  4.             //包含判斷 
  5.             for (int i = 0; i < 3; i++) 
  6.             { 
  7.                 if (*ptr++ == 11) 
  8.                 { 
  9.                     Console.WriteLine(" 11 存在 數(shù)組中"); 
  10.                 } 
  11.             } 

 

后面的兩個(gè)問題就更加復(fù)雜了,既然 Span 是統(tǒng)一訪問,就應(yīng)該用 Span 來接 stackalloc,代碼如下:

  1. Span<int> span = stackalloc int[3] { 10, 11, 12 }; 
  2.  
  3.             //1. 是否包含 
  4.             var hasNum = span.Contains(11); 
  5.  
  6.             //2. 反轉(zhuǎn) 
  7.             span.Reverse(); 
  8.  
  9.             //3. 剔除尾部 
  10.             span.Trim(12); 

這就很了,你既不需要接觸指針,又能完成指針的大部分操作,而且還特別便捷,佩服,最后來驗(yàn)證一下 int[] 是否真的在 線程棧 上。

  1. 0:000> !clrstack -l 
  2. 000000ED7737E4B0 00007FF7C4EA16AD DataStruct.Program.Main(System.String[]) [E:\net5\ConsoleApp2\ConsoleApp1\Program.cs @ 28] 
  3.     LOCALS: 
  4.         0x000000ED7737E570 = 0x000000ed7737e4d0 
  5.         0x000000ED7737E56C = 0x0000000000000001 
  6.         0x000000ED7737E558 = 0x000000ed7737e4d0 
  7.  
  8. 0:000> dp 0x000000ed7737e4d0 
  9. 000000ed`7737e4d0  0000000b`0000000c 00000000`0000000a 

從 Locals 處的 0x000000ED7737E570 = 0x000000ed7737e4d0 可以看到 key / value 是非常相近的,說明在棧上無疑。

從最后一行 a,b,c 可看出對(duì)應(yīng)的就是數(shù)組中的 10,11,12。

2. 非托管堆內(nèi)存

說到非托管內(nèi)存,讓我想起了當(dāng)年 C# 調(diào)用 C++ 的場(chǎng)景,代碼到處充斥著類似下面的語句:

  1. private bool SendMessage(int messageType, string ip, string port, int length, byte[] messageBytes) 
  2.         { 
  3.             bool result = false
  4.             if (windowHandle != 0) 
  5.             { 
  6.                 var bytes = new byte[Const.MaxLengthOfBuffer]; 
  7.                 Array.Copy(messageBytes, bytes, messageBytes.Length); 
  8.  
  9.                 int sizeOfType = Marshal.SizeOf(typeof(StClientData)); 
  10.  
  11.                 StClientData stData = new StClientData 
  12.                 { 
  13.                     Ip = GlobalConvert.IpAddressToUInt32(IPAddress.Parse(ip)), 
  14.                     Port = Convert.ToInt16(port), 
  15.                     Length = Convert.ToUInt32(length), 
  16.                     Buffer = bytes 
  17.                 }; 
  18.  
  19.  
  20.                 int sizeOfStData = Marshal.SizeOf(stData); 
  21.  
  22.                 IntPtr pointer = Marshal.AllocHGlobal(sizeOfStData); 
  23.  
  24.                 Marshal.StructureToPtr(stData, pointer, true); 
  25.  
  26.                 CopyData copyData = new CopyData 
  27.                 { 
  28.                     DwData = (IntPtr)messageType, 
  29.                     CbData = Marshal.SizeOf(sizeOfType), 
  30.                     LpData = pointer 
  31.                 }; 
  32.  
  33.                 SendMessage(windowHandle, WmCopydata, 0, ref copyData); 
  34.  
  35.                 Marshal.FreeHGlobal(pointer); 
  36.  
  37.                 string data = GlobalConvert.ByteArrayToHexString(messageBytes); 
  38.                 CommunicationManager.Instance.SendDebugInfo(new DataSendEventArgs() { Data = data }); 
  39.  
  40.                 result = true
  41.             } 
  42.             return result; 
  43.         } 

上面代碼中的: IntPtr pointer = Marshal.AllocHGlobal(sizeOfStData); 和 Marshal.FreeHGlobal(pointer) 就用到了非托管內(nèi)存,從現(xiàn)在開始你就可以用 Span 來接 Marshal.AllocHGlobal 分配的非托管內(nèi)存啦!,如下代碼所示:

  1. class Program 
  2.     { 
  3.         static unsafe void Main(string[] args) 
  4.         { 
  5.             var ptr = Marshal.AllocHGlobal(3); 
  6.  
  7.             //將 ptr 轉(zhuǎn)換為 span 
  8.             var span = new Span<byte>((byte*)ptr, 3) { [0] = 10, [1] = 11, [2] = 12 }; 
  9.  
  10.             //然后在  span 中可以進(jìn)行各種操作了。。。 
  11.  
  12.             Marshal.FreeHGlobal(ptr); 
  13.         } 
  14.     } 

這里我也用 windbg 給大家看一下 未托管內(nèi)存 在內(nèi)存中是個(gè)什么樣子。

  1. 0:000> !clrstack -l 
  2. OS Thread Id: 0x3b10 (0) 
  3.         Child SP               IP Call Site 
  4. 000000A51777E758 00007ff89cf7c184 [InlinedCallFrame: 000000a51777e758] Interop+Kernel32.ReadFile(IntPtr, Byte*, Int32, Int32 ByRef, IntPtr) 
  5. 000000A51777E758 00007ff7c4654dd8 [InlinedCallFrame: 000000a51777e758] Interop+Kernel32.ReadFile(IntPtr, Byte*, Int32, Int32 ByRef, IntPtr) 
  6. 000000A51777E720 00007FF7C4654DD8 ILStubClass.IL_STUB_PInvoke(IntPtr, Byte*, Int32, Int32 ByRef, IntPtr) 
  7. 000000A51777E9E0 00007FF7C46511D0 DataStruct.Program.Main(System.String[]) [E:\net5\ConsoleApp2\ConsoleApp1\Program.cs @ 26] 
  8.     LOCALS: 
  9.         0x000000A51777EA58 = 0x0000027490144760 
  10.         0x000000A51777EA48 = 0x0000027490144760 
  11.         0x000000A51777EA38 = 0x0000027490144760 
  12.  
  13. 0:000> dp 0x0000027490144760 
  14. 00000274`90144760  abababab`ab0c0b0a abababab`abababab         

最后一行的 0c0b0a 這就是低位到高位的 10,11,12 三個(gè)數(shù),接下來從 Locals 處 0x000000A51777EA58 = 0x0000027490144760 可以看出,這個(gè)key,value 相隔十萬八千里,說明肯定不在棧內(nèi)存中,繼續(xù)用 windbg 鑒別一下 0x0000027490144760 是否是托管堆上,可以用 !eeheap -gc 查看托管堆地址范圍,如下代碼:

  1. 0:000> !eeheap -gc 
  2. Number of GC Heaps: 1 
  3. generation 0 starts at 0x00000274901B1030 
  4. generation 1 starts at 0x00000274901B1018 
  5. generation 2 starts at 0x00000274901B1000 
  6. ephemeral segment allocation context: none 
  7.          segment             begin         allocated              size 
  8. 00000274901B0000  00000274901B1000  00000274901C5370  0x14370(82800) 
  9. Large object heap starts at 0x00000274A01B1000 
  10.          segment             begin         allocated              size 
  11. 00000274A01B0000  00000274A01B1000  00000274A01B5480  0x4480(17536) 
  12. Total Size:              Size: 0x187f0 (100336) bytes. 
  13. ------------------------------ 
  14. GC Heap Size:    Size: 0x187f0 (100336) bytes. 

從上面信息可以看到,0x0000027490144760 明顯不在:3代堆:00000274901B1000 ~ 00000274901C5370 和 大對(duì)象堆:00000274A01B1000 ~ 00000274A01B5480 區(qū)間范圍內(nèi)。

3. 托管堆內(nèi)存

用 Span 統(tǒng)一托管內(nèi)存訪問那是相當(dāng)簡(jiǎn)單了,如下代碼所示:

Span span = new byte[3] { 10, 11, 12 };

同樣,你有了Span,你就可以使用 Span 自帶的各種方法,這里就不多介紹了,大家有興趣可以實(shí)操一下。

三:總結(jié)

總的來說,這一篇主要是從思想上帶大家一起認(rèn)識(shí) Span,以及如何用 Span 對(duì)接 三大區(qū)域內(nèi)存,關(guān)于 Span 的好處以及源碼解析,后面上專門的文章吧!

本文轉(zhuǎn)載自微信公眾號(hào)「 一線碼農(nóng)聊技術(shù)」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系 一線碼農(nóng)聊技術(shù)公眾號(hào)。

 

 

責(zé)任編輯:武曉燕 來源: 一線碼農(nóng)聊技術(shù)
相關(guān)推薦

2021-11-01 07:50:44

TomcatWeb應(yīng)用

2022-06-06 07:52:00

Python大風(fēng)車

2021-10-08 13:38:23

手機(jī)系統(tǒng)鴻蒙

2023-03-03 09:11:55

軟件開發(fā)NASA

2022-04-08 08:11:28

Python代碼

2017-02-23 08:00:04

智能語音Click

2018-04-11 14:30:33

2018-05-14 22:58:14

戴爾

2021-03-01 12:06:12

Nginx命令Linux

2011-06-21 11:16:24

cc++

2025-01-09 11:10:15

2011-04-13 16:50:54

CC++內(nèi)存

2023-09-08 09:12:57

內(nèi)存緩存圖像

2023-05-06 06:47:46

Bing聊天機(jī)器人

2023-11-01 08:07:42

.NETC#

2019-02-12 11:07:49

2024-02-26 13:47:00

C#Socket數(shù)據(jù)接收

2024-02-26 12:42:40

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)