日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > C# >内容正文

C#

用 Span 对 C# 进程中三大内存区域进行统一访问 ,太厉害了!

發(fā)布時(shí)間:2023/12/4 C# 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 用 Span 对 C# 进程中三大内存区域进行统一访问 ,太厉害了! 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

一:背景

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)存訪問(wèn),包括:棧內(nèi)存, 托管堆內(nèi)存, 非托管堆內(nèi)存,畫個(gè)圖如下:

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

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

1. 棧內(nèi)存

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

static?void?Main(string[]?args){int?i?=?10;long?j?=?20;List<string>?list?=?new?List<string>();}

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

0:000>?!clrstack?-l OS?Thread?Id:?0x2708?(0)Child?SP???????????????IP?Call?Site 00000072E47CE558?00007ff89cf7c184?[InlinedCallFrame:?00000072e47ce558]?Interop+Kernel32.ReadFile(IntPtr,?Byte*,?Int32,?Int32?ByRef,?IntPtr) 00000072E47CE558?00007ff7c7c03fd8?[InlinedCallFrame:?00000072e47ce558]?Interop+Kernel32.ReadFile(IntPtr,?Byte*,?Int32,?Int32?ByRef,?IntPtr) 00000072E47CE520?00007FF7C7C03FD8?ILStubClass.IL_STUB_PInvoke(IntPtr,?Byte*,?Int32,?Int32?ByRef,?IntPtr) 00000072E47CE7B0?00007FF8541E530D?System.Console.ReadLine() 00000072E47CE7E0?00007FF7C7C0101E?DataStruct.Program.Main(System.String[])?[E:\net5\ConsoleApp2\ConsoleApp1\Program.cs?@?22]LOCALS:0x00000072E47CE82C?=?0x000000000000000a0x00000072E47CE820?=?0x00000000000000140x00000072E47CE818?=?0x0000018015aeab10

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

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

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

  • 查找某一個(gè)數(shù)是否在 int[] 中

  • 反轉(zhuǎn) int[]

  • 剔除尾部的某一個(gè)數(shù)字(比如 12)

就拿第一個(gè)問(wèn)題來(lái)說(shuō),操作指針的代碼如下:

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

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

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

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

0:000>?!clrstack?-l 000000ED7737E4B0?00007FF7C4EA16AD?DataStruct.Program.Main(System.String[])?[E:\net5\ConsoleApp2\ConsoleApp1\Program.cs?@?28]LOCALS:0x000000ED7737E570?=?0x000000ed7737e4d00x000000ED7737E56C?=?0x00000000000000010x000000ED7737E558?=?0x000000ed7737e4d00:000>?dp?0x000000ed7737e4d0 000000ed`7737e4d0??0000000b`0000000c?00000000`0000000a

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

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

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

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

private?bool?SendMessage(int?messageType,?string?ip,?string?port,?int?length,?byte[]?messageBytes){bool?result?=?false;if?(windowHandle?!=?0){var?bytes?=?new?byte[Const.MaxLengthOfBuffer];Array.Copy(messageBytes,?bytes,?messageBytes.Length);int?sizeOfType?=?Marshal.SizeOf(typeof(StClientData));StClientData?stData?=?new?StClientData{Ip?=?GlobalConvert.IpAddressToUInt32(IPAddress.Parse(ip)),Port?=?Convert.ToInt16(port),Length?=?Convert.ToUInt32(length),Buffer?=?bytes};int?sizeOfStData?=?Marshal.SizeOf(stData);IntPtr?pointer?=?Marshal.AllocHGlobal(sizeOfStData);Marshal.StructureToPtr(stData,?pointer,?true);CopyData?copyData?=?new?CopyData{DwData?=?(IntPtr)messageType,CbData?=?Marshal.SizeOf(sizeOfType),LpData?=?pointer};SendMessage(windowHandle,?WmCopydata,?0,?ref?copyData);Marshal.FreeHGlobal(pointer);string?data?=?GlobalConvert.ByteArrayToHexString(messageBytes);CommunicationManager.Instance.SendDebugInfo(new?DataSendEventArgs()?{?Data?=?data?});result?=?true;}return?result;}

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

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

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

0:000>?!clrstack?-l OS?Thread?Id:?0x3b10?(0)Child?SP???????????????IP?Call?Site 000000A51777E758?00007ff89cf7c184?[InlinedCallFrame:?000000a51777e758]?Interop+Kernel32.ReadFile(IntPtr,?Byte*,?Int32,?Int32?ByRef,?IntPtr) 000000A51777E758?00007ff7c4654dd8?[InlinedCallFrame:?000000a51777e758]?Interop+Kernel32.ReadFile(IntPtr,?Byte*,?Int32,?Int32?ByRef,?IntPtr) 000000A51777E720?00007FF7C4654DD8?ILStubClass.IL_STUB_PInvoke(IntPtr,?Byte*,?Int32,?Int32?ByRef,?IntPtr) 000000A51777E9E0?00007FF7C46511D0?DataStruct.Program.Main(System.String[])?[E:\net5\ConsoleApp2\ConsoleApp1\Program.cs?@?26]LOCALS:0x000000A51777EA58?=?0x00000274901447600x000000A51777EA48?=?0x00000274901447600x000000A51777EA38?=?0x00000274901447600:000>?dp?0x0000027490144760 00000274`90144760??abababab`ab0c0b0a?abababab`abababab????????

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

0:000>?!eeheap?-gc Number?of?GC?Heaps:?1 generation?0?starts?at?0x00000274901B1030 generation?1?starts?at?0x00000274901B1018 generation?2?starts?at?0x00000274901B1000 ephemeral?segment?allocation?context:?nonesegment?????????????begin?????????allocated??????????????size 00000274901B0000??00000274901B1000??00000274901C5370??0x14370(82800) Large?object?heap?starts?at?0x00000274A01B1000segment?????????????begin?????????allocated??????????????size 00000274A01B0000??00000274A01B1000??00000274A01B5480??0x4480(17536) Total?Size:??????????????Size:?0x187f0?(100336)?bytes. ------------------------------ 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)存訪問(wèn)那是相當(dāng)簡(jiǎn)單了,如下代碼所示:

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

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

三:總結(jié)

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

總結(jié)

以上是生活随笔為你收集整理的用 Span 对 C# 进程中三大内存区域进行统一访问 ,太厉害了!的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。