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堆起始和结束范围内。
这里验证跟上面的推测完全符合。