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 0x7fff78d754ff
Breakpoint 20: address = 0x00007fff78d754ff
(lldb) c
Process 3029 resuming
Process 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堆起始和结束范围内。

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值