C# unsafe里的fixed是做什么用的?

1.前言
如果你想在托管里面使用非托管指针,那么这个unsafe是一个选择。并且unsafe一般还带了一个fixed关键字,这个东西是干嘛用的呢?如果你看微软官方文档,它只有寥寥几字:临时固定变量以便找到其地址。你是否依然一头雾水?本篇来解析下这个fixed关键字。

2.概述
一:例子
先上一个官方例子:

 static void Main(string[] args) { int[] a = new int[5] { 0x10, 0x20, 0x30, 0x40, 0x50 }; unsafe { fixed (int* p = &a[0]) { int* p2 = p; Console.WriteLine(*p2); p2 += 1; Console.WriteLine(*p2); p2 += 1; Console.WriteLine(*p2); Console.WriteLine("--------"); Console.WriteLine(*p); *p += 1; Console.WriteLine(*p); *p += 1; Console.WriteLine(*p); } } Console.WriteLine("--------"); Console.WriteLine(a[0]); Console.ReadLine(); }

例子fixed括号里面把指针p指向了数组a的第一个元素。注意这里的是&a[0]意即第一个元素的地址。

它用这个fixed是什么意思呢?

二.概念
官方解释:临时固定变量以便找到其地址。
这里我们需要明白几个概念,先回答几个问题
固定的是谁呢?固定的就是这个p指向的值。
这个临时变量是谁呢?那么自然是指针p。
谁找到谁的地址呢?GC找到p指针的地址。

为什么需要固定?因为unsafe里面标记的是非托管代码,而这些代码基本上不受控于GC,当进行GC垃圾回收的时候,指针p指向地址里面的值可能被GC移位了。为了避免进行垃圾回收被移位,所以这里需要固定。

这里依然需要明白,固定之后,它就变成了雷同于固定对象,但不一定是固定对象的定义。

关于这里的固定对象,可以参考:.Net8罕见的技术:固定对象的操作

三.固定对象
这里的固定对象不一定是上面固定对象的概念,至少目前没有证据。而是说通过fixed把对象固定住。
那么它的操作依然是,通过fixed分配的指针p,把它放在GC堆之外,那么它这个p如何回收呢?当fixed的大括号执行完成之后,它会自动回收。它的大括号相当于using{}作用。

这里也验证下p是否在GC堆外面,还是验证前一篇固定对象的方法。
先找到GC堆的起始和结束地址,然后跟p指针比对,看它是否在此范围内。

首先我们直接来到托管Main下面代码

 fixed (int* p = &a[0])

汇编如下:

(lldb) di -s $pc -c 0x30-> 0x7fff78d75470: push rbp 0x7fff78d75471: sub rsp, 0x50 0x7fff78d75475: lea rbp, [rsp + 0x50] 0x7fff78d7547a: vxorps xmm8, xmm8, xmm8 0x7fff78d7547f: vmovdqa xmmword ptr [rbp - 0x40], xmm8 0x7fff78d75484: vmovdqa xmmword ptr [rbp - 0x30], xmm8 0x7fff78d75489: vmovdqa xmmword ptr [rbp - 0x20], xmm8 0x7fff78d7548e: xor eax, eax 0x7fff78d75490: mov qword ptr [rbp - 0x10], rax 0x7fff78d75494: mov qword ptr [rbp - 0x8], rdi 0x7fff78d75498: cmp dword ptr [rip + 0x1df3a9], 0x0 0x7fff78d7549f: je 0x7fff78d754a6 0x7fff78d754a1: call 0x7ffff70834a0 ; JIT_DbgIsJustMyCode at jithelpers.cpp:4489 0x7fff78d754a6: nop  0x7fff78d754a7: movabs rdi, 0x7fff78d3e998 0x7fff78d754b1: mov esi, 0x5 0x7fff78d754b6: call 0x7ffff7072c90 ; JIT_NewArr1VC_MP_FastPortable at jithelpers.cpp:2467 0x7fff78d754bb: mov qword ptr [rbp - 0x30], rax 0x7fff78d754bf: movabs rdi, 0x7fff7910c280 0x7fff78d754c9: call 0x7ffff7078660 ; JIT_GetRuntimeFieldStub at jithelpers.cpp:3391 0x7fff78d754ce: mov qword ptr [rbp - 0x38], rax 0x7fff78d754d2: mov rdi, qword ptr [rbp - 0x30] 0x7fff78d754d6: mov rsi, qword ptr [rbp - 0x38] 0x7fff78d754da: call 0x7ffff74b7c50 ; ArrayNative::InitializeArray at arraynative.cpp:936 0x7fff78d754df: mov rdi, qword ptr [rbp - 0x30] 0x7fff78d754e3: mov qword ptr [rbp - 0x10], rdi 0x7fff78d754e7: nop  0x7fff78d754e8: mov rdi, qword ptr [rbp - 0x10] 0x7fff78d754ec: xor eax, eax 0x7fff78d754ee: cmp eax, dword ptr [rdi + 0x8] 0x7fff78d754f1: jb 0x7fff78d754f8 0x7fff78d754f3: call 0x7ffff707df00 ; JIT_RngChkFail at jithelpers.cpp:4062 0x7fff78d754f8: mov esi, eax 0x7fff78d754fa: lea rdi, [rdi + 4*rsi + 0x10] 0x7fff78d754ff: mov qword ptr [rbp - 0x20], rdi

最后一行

(lldb) b 0x7fff78d754ffBreakpoint 20: address = 0x00007fff78d754ff(lldb) cProcess 3029 resumingProcess 3029 stopped* thread #1, name = 'clrrun', stop reason = breakpoint 19.1 20.1 frame #0: 0x00007fff78d754ff-> 0x7fff78d754ff: mov qword ptr [rbp - 0x20], rdi 0x7fff78d75503: mov rdi, qword ptr [rbp - 0x20] 0x7fff78d75507: mov qword ptr [rbp - 0x48], rdi 0x7fff78d7550b: mov rdi, qword ptr [rbp - 0x48]

rdi即p指针的值,看下它是多少

(lldb) p/x $rdi(unsigned long) $11 = 0x00007fbf6a808b08

记住它:0x00007fbf6a808b08

在handletablescan.cpp:442(这里不明白,可以参考上一篇文章:.Net8罕见的技术:固定对象的操作处断点跟踪到is_in_find_object_range函数,查看里面的GC堆的范围

(lldb) p/x g_gc_lowest_address(uint8_t *) $12 = 0x00007fbf68000000 ""(lldb) p/x g_gc_highest_address(uint8_t *) $13 = 0x00007fff68000000 "0"

GC的起始和结束都是:0x00007fbf68000000,说明它里面目前没有放在GC堆的对象。

而p的地址是:0x00007fbf6a808b08。很明显p不在GC堆起始和结束范围内。

这里验证跟上面的推测完全符合。


展开阅读全文

页面更新:2024-01-31

标签:范围内   括号   指针   变量   例子   对象   概念   结束   代码   地址

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号

Top