一:背景

十年的市北网站建设经验,针对设计、前端、开发、售后、文案、推广等六对一服务,响应快,48小时及时工作处理。全网营销推广的优势是能够根据用户设备显示端的尺寸不同,自动调整市北建站的显示方式,使网站能够适用不同显示终端,在浏览器中调整网站的宽度,无论在任何一种浏览器上浏览网站,都能展现优雅布局与设计,从而大程度地提升浏览体验。成都创新互联从事“市北网站设计”,“市北网站推广”以来,每个客户项目都认真落实执行。
1. 讲故事
前段时间写了几篇 C# 漫文,评论留言中有很多朋友多次提到 Span,周末抽空看了下,确实是一个非常的新结构,让我想到了当年的WCF,它统一了.NET下各种零散的分布式技术,包括:.NET Remoteing,WebService,NamedPipe,MSMQ,而这里的 Span 统一了 C# 进程中的三大块内存访问,包括:栈内存, 托管堆内存, 非托管堆内存,画个图如下:
接下来就和大家具体聊聊这三大块的内存统一访问。
二:进程中的三大块内存解析
1. 栈内存
大家应该知道方法内的局部变量是存放在栈上的,而且每一个线程默认会被分配 1M 的内存空间,我举个例子:
- static void Main(string[] args)
 - {
 - int i = 10;
 - long j = 20;
 - List
 list = new List (); - }
 
上面 i,j 的值都是存于栈上,list的堆上内存地址也是存于栈上,为了看个究竟,可以用 windbg 验证一下:
- 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 = 0x000000000000000a
 - 0x00000072E47CE820 = 0x0000000000000014
 - 0x00000072E47CE818 = 0x0000018015aeab10
 
通过 clrstack -l 查看线程栈,最后三行可以明显的看到 0a -> 10, 14 -> 20 , 0xxxxxxb10 => list堆地址,除了这些简单类型,还可以在栈上分配复杂类型,这里就要用到 stackalloc 关键词, 如下代码:
- int* ptr = stackalloc int[3] { 10, 11, 12 };
 
问题就在这里,指针类型虽然灵活,但是做任何事情都比较繁琐,比如说:
就拿第一个问题来说,操作指针的代码如下:
- //指针接收
 - int* ptr = stackalloc int[3] { 10, 11, 12 };
 - //包含判断
 - for (int i = 0; i < 3; i++)
 - {
 - if (*ptr++ == 11)
 - {
 - Console.WriteLine(" 11 存在 数组中");
 - }
 - }
 
后面的两个问题就更加复杂了,既然 Span 是统一访问,就应该用 Span 来接 stackalloc,代码如下:
- Span
 span = stackalloc int[3] { 10, 11, 12 }; - //1. 是否包含
 - var hasNum = span.Contains(11);
 - //2. 反转
 - span.Reverse();
 - //3. 剔除尾部
 - span.Trim(12);
 
这就很了,你既不需要接触指针,又能完成指针的大部分操作,而且还特别便捷,佩服,最后来验证一下 int[] 是否真的在 线程栈 上。
- 0:000> !clrstack -l
 - 000000ED7737E4B0 00007FF7C4EA16AD DataStruct.Program.Main(System.String[]) [E:\net5\ConsoleApp2\ConsoleApp1\Program.cs @ 28]
 - LOCALS:
 - 0x000000ED7737E570 = 0x000000ed7737e4d0
 - 0x000000ED7737E56C = 0x0000000000000001
 - 0x000000ED7737E558 = 0x000000ed7737e4d0
 - 0:000> dp 0x000000ed7737e4d0
 - 000000ed`7737e4d0 0000000b`0000000c 00000000`0000000a
 
从 Locals 处的 0x000000ED7737E570 = 0x000000ed7737e4d0 可以看到 key / value 是非常相近的,说明在栈上无疑。
从最后一行 a,b,c 可看出对应的就是数组中的 10,11,12。
2. 非托管堆内存
说到非托管内存,让我想起了当年 C# 调用 C++ 的场景,代码到处充斥着类似下面的语句:
- 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) 就用到了非托管内存,从现在开始你就可以用 Span 来接 Marshal.AllocHGlobal 分配的非托管内存啦!,如下代码所示:
- class Program
 - {
 - static unsafe void Main(string[] args)
 - {
 - var ptr = Marshal.AllocHGlobal(3);
 - //将 ptr 转换为 span
 - var span = new Span
 ((byte*)ptr, 3) { [0] = 10, [1] = 11, [2] = 12 }; - //然后在 span 中可以进行各种操作了。。。
 - Marshal.FreeHGlobal(ptr);
 - }
 - }
 
这里我也用 windbg 给大家看一下 未托管内存 在内存中是个什么样子。
- 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 = 0x0000027490144760
 - 0x000000A51777EA48 = 0x0000027490144760
 - 0x000000A51777EA38 = 0x0000027490144760
 - 0:000> dp 0x0000027490144760
 - 00000274`90144760 abababab`ab0c0b0a abababab`abababab
 
最后一行的 0c0b0a 这就是低位到高位的 10,11,12 三个数,接下来从 Locals 处 0x000000A51777EA58 = 0x0000027490144760 可以看出,这个key,value 相隔十万八千里,说明肯定不在栈内存中,继续用 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: none
 - segment begin allocated size
 - 00000274901B0000 00000274901B1000 00000274901C5370 0x14370(82800)
 - Large object heap starts at 0x00000274A01B1000
 - segment 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 和 大对象堆:00000274A01B1000 ~ 00000274A01B5480 区间范围内。
3. 托管堆内存
用 Span 统一托管内存访问那是相当简单了,如下代码所示:
Span span = new byte[3] { 10, 11, 12 };
同样,你有了Span,你就可以使用 Span 自带的各种方法,这里就不多介绍了,大家有兴趣可以实操一下。
三:总结
总的来说,这一篇主要是从思想上带大家一起认识 Span,以及如何用 Span 对接 三大区域内存,关于 Span 的好处以及源码解析,后面上专门的文章吧!
本文转载自微信公众号「 一线码农聊技术」,可以通过以下二维码关注。转载本文请联系 一线码农聊技术公众号。
                网站题目:用Span对C#进程中三大内存区域进行统一访问,太厉害了!
                
                路径分享:http://www.csdahua.cn/qtweb/news29/202679.html
            
网站建设、网络推广公司-快上网,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 快上网