1:背景1.讲故事我前段时间写了一些C#漫画。很多小伙伴在评论和评论中提到了Span。周末抽空读了一遍。确实是一个很新的结构,让我想起了它。当年WCF统一了.NET下各种分散的分布式技术,包括:.NETRemoteing、WebService、NamedPipe、MSMQ,这里的Span统一了C#进程中访问内存的三大块,包括:栈内存、托管堆内存,非托管堆内存,画个图如下:接下来说说这三大块内存的统一访问。二:进程中三大内存块分析1.栈内存大家应该都知道,方法中的局部变量是存放在栈中的,每个线程默认都会分配1M的内存空间。举个例子:staticvoidMain(string[]args){inti=10;longj=20;Listlist=newList();}上面i和j的值都存储了在栈上,list的堆内存地址也存储在栈上,为了看个研究实,可以用windbg验证一下:0:000>!clrstack-lOSThreadId:0x2708(0)ChildSPIPCallSite00000072E47CE55800007ff89cf7c184:InlinedCall7007[020dInterFrame7007]+Kernel32.ReadFile(IntPtr,Byte*,Int32,Int32ByRef,IntPtr)00000072E47CE55800007ff7c7c03fd8[InlinedCallFrame:00000072e47ce558]Interop+Kernel32.ReadFile(IntPtr,Byte*,Int32,Int32ByRef,IntPtr)00000072E47CE52000007FF7C7C03FD8ILStubClass.IL_STUB_PInvoke(IntPtr,Byte*,Int32,Int32ByRef,IntPtr)00000072E47CE7B000007FF8541E530DSystem.Console.ReadLine()00000072E47CE7E000007FF7C7C0101EDataStruct.Program.Main(System.String[])[E:\net5\ConsoleApp2\ConsoleApp1\Program.cs@22]LOCALS:0x00000072E47CE82C=0x000000000000000a0x00000072E47CE820=0x00000000000000140x00000072E47CE818=0x0000018015aeab10通过clrstack-l查看线路栈,最后三行可以明显的看到0a->10,14->20,0xxxxxxb10=>列出堆地址,除了这些简单类型,复杂类型也可以在栈上分配,这里需要用到stackalloc关键字,代码如下:int*ptr=stackallocint[3]{10,11,12};这就是问题。指针类型虽然灵活,但是做任何事情都比较麻烦,例如:在int[]中查找某个数字是否反转了int[]并在末尾去掉某个数字(比如12)拿第一题例如,操作指针的代码如下://指针接收int*ptr=stackallocint[3]{10,11,12};//包括判断for(inti=0;i<3;i++){if(*ptr++==11){Console.WriteLine("数组中存在11");}}后两个问题比较复杂。由于Span是统一接入的,所以应该使用Span来接stackalloc,代码如下:Spanspan=stackallocint[3]{10,11,12};//1。是否包含varhasNum=span.Contains(11);//2.反向span.Reverse();//3.去掉尾巴span.Trim(12);这个很easy,不用碰指针,就可以完成指针的大部分操作,很方便,佩服,最后验证一下int[]是否真的在线程栈上0:000>!clrstack-l000000ED7737E4B000007FF7C4EA16ADDataStruct.Program.Main(System.String[])[E:\net5\ConsoleApp2\ConsoleApp1\Program.cs@28]LOCALS:0x000000ED7737E570=0x000000ed7737e4d00x000000ED7737E56C=0x00000000000000010x000000ED7737E558=0x000000ed7737e4d00:000>dp0x000000ed7737e4d0000000ed`7737e4d00000000b`0000000c00000000`0000000a从Locals中的0x000000ED7737E570=0x000000ed7737e4d0可以看出key/value非常相似,说明栈上没有疑问。从最后一行a,b,c可以看出,它们分别对应数组中的10、11、12。2.非托管堆内存说到非托管内存,让我想起了C#调用C++时的场景。代码中充满了类似于以下内容的语句:privateboolSendMessage(intmessageType,stringip,stringport,intlength,byte[]messageBytes){boolresult=false;if(windowHandle!=0){varbytes=newbyte[Const.MaxLengthOfBuffer];Array。复制(messageBytes,字节,messageBytes.Length);intsizeOfType=Marshal.SizeOf(typeof(StClientData));StClientDatastData=newStClientData{Ip=GlobalConvert.IpAddressToUInt32(IPAddress.Parse(ip)),Port=Convert.ToInt16(端口),Length=Convert.ToUInt32(length),Buffer=bytes};intsizeOfStData=Marshal.SizeOf(stData);IntPtrpointer=Marshal.AllocHGlobal(sizeOfStData);Marshal.StructureToPtr(stData,pointer,true);CopyDatacopyData=newCopyData{DwData=(IntPtr)messageType,CbData=Marshal.SizeOf(sizeOfType),LpData=pointer};SendMessage(windowHandle,WmCopydata,0,refcopyData);Marshal.FreeHGlobal(指针);stringdata=GlobalConvert.ByteArrayToHexString(messageBytes);CommunicationManager.Instance.SendDebugInfo(newDataSendEventArgs(){Data=data});result=true;}returnresult;}在上面的代码中:IntPtrpointer=Marshal.AllocHGlobal(sizeOfStData);而Marshal.FreeHGlobal(pointer)使用的是非托管内存,以??后可以使用Span来访问Marshal.AllocHGlobal啦!分配的非托管内存,如下代码所示:classProgram{staticunsafevoidMain(string[]args){varptr=Marshal.AllocHGlobal(3);//将ptr转换为spanvarspan=newSpan((byte*)ptr,3){[0]=10,[1]=11,[2]=12};//然后可以在跨度中执行各种操作。.Marshal.FreeHGlobal(ptr);}}这里我也用windbg给大家看看非托管内存在内存中是什么样子的。0:000>!clrstack-lOSThreadId:0x3b10(0)ChildSPIPCallSite000000A51777E75800007ff89cf7c184[InlinedCallFrame:000000a51777e758]Interop+Kernel32.ReadFile(IntPtr,Byte*,Int32,Int32ByRef,IntPtr)000000A51777E75800007ff7c4654dd8[InlinedCallFrame:000000a51777e758]Interop+Kernel32.ReadFile(IntPtr,Byte*,Int32,Int32ByRef,IntPtr)000000A51777E72000007FF7C4654DD8ILStubClass.IL_STUB_PInvoke(IntPtr,Byte*,Int32,Int32ByRef,IntPtr)000000A51777E9E000007FF7C46511D0DataStruct.Program.Main(System.String[])[E:\net5\ConsoleApp2\ConsoleApp1\Program.cs@26]LOCALS:0x000000A51777EA58=0x00000274901447600x000000A51777EA48=0x00000274901447600x000000A51777EA38=0x00000274901447600:000>dp0x000002749014476000000274`90144760abababab`ab0c0b0aabababab`abababab最后一行的0c0b0a这就是低位到高位的10,11,12三个数,接下来从Locals处0x000000A51777EA58=0x0000027490144760可以看Itshowsthatthekeyandvalueareseparatedbyhundredsofthousandsofmiles,whichmeansthatitisdefinitelynotinthestackmemory.Continuetousewindbgtoidentifywhether0x0000027490144760isonthemanagedheap.Youcanuse!eeheap-gctoviewthemanagedheapaddressrange,thefollowingcode:0:000>!eeheap-gcNumberofGCHeaps:1generation0startsat0x00000274901B1030generation1startsat0x00000274901B1018generation2startsat0x00000274901B1000ephemeralsegmentallocationcontext:nonesegmentbeginallocatedsize00000274901B000000000274901B100000000274901C53700x14370(82800)Largeobjectheapstartsat0x00000274A01B1000segmentbeginallocatedsize00000274A01B000000000274A01B100000000274A01B54800x4480(17536)TotalSize:Size:0x187f0(100336)bytes.------------------------------GCHeapSize:Size:0x187f0(100336)bytes.从上面信息可以看到,0x0000027490144760明显不在:3代堆:00000274901B1000~00000274901C5370和大对象堆:00000274A01B1000~00000274A01B5480区间范围内3.托管堆内存用Span统一托管Memoryaccessisquitesimple,asshowninthefollowingcode:Spanspan=newbyte[3]{10,11,12};Similarly,ifyouhaveaSpan,youcanusevariousmethodsthatcomewiththeSpan,hereisNotmuchtointroduce,ifyouareinterested,youcanpracticeit.Three:SummaryIngeneral,thisarticleismainlytobringeveryonetounderstandSpanfromtheideologicalpointofview,andhowtouseSpantoconnectthethreemajorareasofmemory.RegardingthebenefitsofSpanandsourcecodeanalysis,therewillbeaspecialarticlelater!ThisarticleisreproducedfromWechat公众号“First-lineCodeFarmerChatTechnology”,youcanfollowthroughthefollowingQRcode.Toreprintthisarticle,pleasecontactthefirst-linecodefarmerstochatabouttechnology公众号.