Bootstrap
C/C++这样相对底层的语言,要使用指针直接操作内存,但在像C#这样的高级语言中,基本上很少使用指针,都是靠垃圾回收器自动回收内存。使我们的编程变得更简单,不用直接操作内存。但是一些像图像编程,直接用指针性能会要高一些,也减少了对GC的压力。

1、在vs中使用指针,还要对项目做一些设置,需要允许不安全代码

在项目上右键属性,在点击 "生成" ,勾选允许不安代码,有些vs中那个选项可能是英文的,需要注意一下,如下图,


2、C#指针的基础语法

 在代码中用指针有两种方式,一种是unsafe {  },表明括中的代码是不安全的,另一种是在方法或属性上加入unsafe关键字。两种方法分别如下

1)

    private static IntPtr GetMemoryAddress(MethodBase mb)
        {
            unsafe
            {
                RuntimeHelpers.PrepareMethod(mb.MethodHandle);
                if ((Environment.Version.Major >= 4) || ((Environment.Version.Major == 2) && (Environment.Version.MinorRevision >= 3053)))
                {
                    return new IntPtr(((int*)mb.MethodHandle.Value.ToPointer() + 2));
                }
                UInt64* location = (UInt64*)(mb.MethodHandle.Value.ToPointer());
                int index = (int)(((*location) >> 32) & 0xFF);
                if (IntPtr.Size == 8)
                {
                    ulong* classStart = (ulong*)mb.DeclaringType.TypeHandle.Value.ToPointer();
                    ulong* address = classStart + index + 10;
                    return new IntPtr(address);
                }
                else
                {
                    uint* classStart = (uint*)mb.DeclaringType.TypeHandle.Value.ToPointer();
                    uint* address = classStart + index + 10;
                    return new IntPtr(address);
                }
            }
        }

 2)

  private static unsafe void MemoryPatching(MethodBase miEvaluation, MethodBase miLicensed)
        {
            IntPtr IntPtrEval = GetMemoryAddress(miEvaluation);
            IntPtr IntPtrLicensed = GetMemoryAddress(miLicensed);
            if (IntPtr.Size == 8)
                *((long*)IntPtrEval.ToPointer()) = *((long*)IntPtrLicensed.ToPointer());
            else
                *((int*)IntPtrEval.ToPointer()) = *((int*)IntPtrLicensed.ToPointer());
        }

在看一下具体怎么在C#中使用指针,怎么分配内存,和怎么释放内存。

1)要用指针操作托管堆上的值类型,需要用到 fixed关键字

  public unsafe class Coder
     {
        public int Age;
        public void SetAge(int age)
        {
            fixed (int* p = &Age)
            {
                *p = age;
            }
        }
    }

2)用指针操作栈上的值类型

             int* p = &maxValue;
             *p = 20;
             Console.WriteLine(p->ToString());

3)指针操作非托管堆上的内存,非托管的内存需要手动释放,GC就不管理了,需要手动管理。

System.Runtime.InteropServices.Marshal.AllocHGlobal()//用来从非托管堆上分配内存
System.Runtime.InteropServices.Marshal.FreeHGlobal(handle)//用来释放从非托管对上分配的内存
       IntPtr handle = System.Runtime.InteropServices.Marshal.AllocHGlobal(4);
             Int32* p = (Int32*)handle;
             *p = 20;
             Console.WriteLine(p->ToString());
             System.Runtime.InteropServices.Marshal.FreeHGlobal(handle);

    还可以 用IDispose接口管理内存,代码如下:

   public unsafe class UnmanagedMemory : IDisposable
        {
            public int Count { get; private set; }
            private byte* Handle;
            private bool _disposed = false;
            public UnmanagedMemory(int bytes)
            {
                Handle = (byte*) System.Runtime.InteropServices.Marshal.AllocHGlobal(bytes);
                Count = bytes;
            }
            public void Dispose()
            {
                Dispose(true);
                GC.SuppressFinalize(true);
            }
            protected virtual void Dispose( bool isDisposing )
            {
                if (_disposed) return;
                if (isDisposing)
                {
                    if (Handle != null)
                    {
                        System.Runtime.InteropServices.Marshal.FreeHGlobal((IntPtr)Handle);
                    }
                }
                _disposed = true;
            }
            ~UnmanagedMemory()
           {
              Dispose( false );
           }
        }

    使用方法:

 using (UnmanagedMemory memory = new UnmanagedMemory(10))
            {
                int* p = (int*)memory.Handle;
                *p = 20;
                Console.WriteLine(p->ToString());
            }

4)使用stackalloc关键字在栈中分配内存,一般况下,是比堆中的内存速度快,在此情况下栈中分配的内存,方法退出内存自动释放,不用担心内存泄漏。

   /* 使用指针优化性能 */
    //使用关键字stackalloc(指示.net运行库分配堆栈上一定内存)创建基于堆栈的高效数组
    unsafe
    {
        /*
         * stackalloc命令只分配堆栈内存而已,不会把内存初始化为任何默认值
         * 1.其后紧跟要存储数据类型名(该数据类型必须是一值类型)
         * 2.分配的字节数为变量个数*sizeof(数据类型)
        */
        int* pInt = stackalloc int[10];
        *pInt = 0;
        *(pInt + 1) = 1;
        pInt[2] = 5;         //表达式被编译为*(pInt + 2)
        pInt[50] = 100;      //不报错,使用stackalloc声明一相同数组,对数组边界检查,这数组没封装任何对象
  }

3、C# 指针操作的几个缺点

    缺点1:只能用来操作值类型
    .Net中,引用类型的内存管理全部是由GC管理的,无法取得其地址,因此,无法用指针来操作引用类型。
    缺点2:泛型不支持指针类型
    C# 中泛型不支持指针类型。可以考虑其它替代方案解决这个限制。
    缺点3:没有函数指针
    幸运的是,C# 中有delegate,delegate 支持支持指针类型,lambda 表达式也支持指针。