验证fixed关键字效果的小实验
2009-11-29 21:11 Jeffrey Zhao 阅读(5777) 评论(35) 编辑 收藏 举报之前谈到String连接操作的性能,其中会涉及到unsafe操作,而unsafe操作必然会涉及到指针,于是fixed关键字也应运而生。fixed关键字是用来pin住一个引用地址的,因为我们知道CLR的垃圾收集器会改变某些对象的地址,因此在改变地址之后指向那些对象的引用就要随之改变。这种改变是对于程序员来说是无意识的,因此在指针操作中是不允许的。否则,我们之前已经保留下的地址,在GC后就无法找到我们所需要的对象。现在就来我们就来做一个小实验,验证fixed关键字的效果。
当然,这个实验很简单,简单地可能会让您笑话。首先我们来准备一个SomeClass类:
public class SomeClass { public int Field; }
然后准备一段代码:
private static unsafe void GCOutOfFixedBlock() { var a = new int[100]; var c = new SomeClass(); fixed (int* ptr = &c.Field) { PrintAddress("Before GC", (int)ptr); } GC.Collect(2); fixed (int* ptr = &c.Field) { PrintAddress("After GC", (int)ptr); } } private static void PrintAddress(string name, int address) { Console.Write(name + ": 0x"); Console.WriteLine(address.ToString("X")); }
在GCOutOfFixedBlock方法中,我们首先分配一个长度为100的int数组,然后新建一个SomeClass对象。新建数组的目的在于制造“垃圾”,目的是在调用GC.Collect方法时改变SomeClass对象在堆中的位置。由于垃圾回收发生在fixed代码块之外,这样我们前后两次打印出的值便是不同的:
Before GC: 0x1A058C0 After GC: 0x1975DF4
值得注意的是,这段代码必须在Release模式下进行编译,让CLR执行代码时进行优化,这样CLR便会在垃圾回收时发现a数组已经是垃圾了(因为后面的代码不会用它),于是会将其回收——否则便无法看出地址改变的效果来。那么,我们重写一段代码:
private static unsafe void GCInsideFixedBlock() { var a = new int[100]; var c = new SomeClass(); fixed (int* ptr = &c.Field) { PrintAddress("Before GC", (int)ptr); GC.Collect(2); } fixed (int* ptr = &c.Field) { PrintAddress("After GC", (int)ptr); } }
结果如下:
Before GC: 0x1B558C0 After GC: 0x1B558C0
由于GC发生在fixed代码块内部,因此c对象被pin在堆上了,于是GC前后c对象的地址没变,这就是fixed的作用。那么,下面这段代码运行结果是什么呢?
private static unsafe void Mixed() { var a = new int[100]; var c1 = new SomeClass(); var c2 = new SomeClass(); fixed (int* ptr1 = &c1.Field) { PrintAddress("Before GC", (int)ptr1); } fixed (int* ptr2 = &c2.Field) { PrintAddress("Before GC (fixed)", (int)ptr2); GC.Collect(2); } fixed (int* ptr1 = &c1.Field) { PrintAddress("After GC", (int)ptr1); } fixed (int* ptr2 = &c2.Field) { PrintAddress("After GC (fixed)", (int)ptr2); } }
至于为什么是这个结果,那便和CLR实现方式有关了具体参见文章下方讨论。