C# 指针 研究篇

版权声明:欢迎转载与留言提问 https://blog.csdn.net/qq_25439417/article/details/82110528

原由:

在图像处理中,如果将图像从内存中转成bitmap,在做处理的话,耗时太长,考虑直接在内存中,对图像进行处理,记得C#的优点就是不需要太多的指针,但是这也是做快速算法的缺点,特此来研究一下C#指针相关的知识。最近项目中,需要对图像进行快速处理,并显示在双屏界面中。

开端:

    将C#图像库的基础部分开源了(https://github.com/xiaotie/GebImage)。这个库比较简单,且离成熟还有一段距离,但它是一种新的开发模式的探索:以指针和非托管内存为主的C#程序开发。 以指针和非托管内存为主的C#程序开发,无论对.Net程序员来说,还是对传统的C/C++程序员来说,均属异类。然而这种方法在很多场景下是非常有效的,尤其是图像编程,所谓谈笑间,樯橹灰飞烟灭,不外如是。

 在C#中使用指针,需要在项目属性中选中“Allow unsafe code”:

1.基础篇

    接着,还需要在使用指针的代码的上下文中使用unsafe关键字,表明这是一段unsafe代码。
    可以用unsafe {  } 将代码围住,如:

unsafe
                     {
                         new ImageArgb32(path).ShowDialog("原始图像")
                             .ToGrayscaleImage().ShowDialog("灰度图像")
                             .ApplyOtsuThreshold().ShowDialog("二值化图像")
                             .ToImageArgb32()
                             .ForEach((Argb32* p) => { if (p->Red == 255) *p = Argb32.RED; })
                             .ShowDialog("染色");
                     }

也可在方法或属性上加入unsafe关键字,如:

     private unsafe void btnSubmit_Click(object sender, EventArgs e)

 也可在class或struct 上加上unsafe 关键字,如:

 public partial unsafe class FrmDemo1 : Form

 指针配合fixed关键字可以操作托管堆上的值类型,如:

public unsafe class Person
    {
        public int Age;

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

1、fixed 语句禁止垃圾回收器重定位可移动的变量。fixed 语句只能出现在不安全的上下文中。Fixed 还可用于创建固定大小的缓冲区。
2、fixed 语句设置指向托管变量的指针并在 statement 执行期间“钉住”该变量。如果没有 fixed 语句,则指向可移动托管变量的指针的作用很小,因为垃圾回收可能不可预知地重定位变量。C# 编译器只允许在 fixed 语句中分配指向托管变量的指针。
3、执行完语句中的代码后,任何固定变量都被解除固定并受垃圾回收的制约。因此,不要指向 fixed 语句之外的那些变量。

    指针可以操作栈上的值类型,如:

             int age = 0;
             int* p = &age;
             *p = 20;
             MessageBox.Show(p->ToString());

    指针也可以操作非托管堆上的内存,如:

             IntPtr handle = System.Runtime.InteropServices.Marshal.AllocHGlobal(4);
             Int32* p = (Int32*)handle;
             *p = 20;
             MessageBox.Show(p->ToString());
             System.Runtime.InteropServices.Marshal.FreeHGlobal(handle);

 System.Runtime.InteropServices.Marshal.AllocHGlobal 用来从非托管堆上分配内存。System.Runtime.InteropServices.Marshal.FreeHGlobal(handle)用来释放从非托管对上分配的内存。这样我们就可以避开GC,自己管理内存了。

2、几种常用用法

    1、使用Dispose模式管理非托管内存

    如果使用非托管内存,建议用Dispose模式来管理内存,这样做有以下好处: 可以手动dispose来释放内存;可以使用using 关键字开管理内存;即使不释放,当Dispose对象被GC回收时,也会收回内存。

    下面是Dispose模式的简单例子:

       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;
                MessageBox.Show(p->ToString());
            }

using可以在对象使用完以后,自动调用Dispose()函数,释放内存

  析构函数 Dispose方法 Close方法
意义 销毁对象 销毁对象 关闭对象资源
调用方式 不能被显示调用,在GC回收是被调用 需要显示调用或者通过using语句 需要显示调用
调用时机 不确定 确定,在显示调用或者离开using程序块 确定,在显示调用时

析构函数 和 Dispose 的说明

  • Dispose需要实现IDisposable接口。  
  • Dispose由开发人员代码调用,而析构函数由GC自动调用。
  • Dispose方法应释放所有托管和非托管资源。而析构函数只应释放非托管资源。因为析构函数由GC来判断调用,当GC判断某个对象不再需要的时候,则调用其析构方法,这时候该对象中可能还包含有其他有用的托管资源。
  • Dispose方法结尾处加上代码“GC.SuppressFinalize(this);”,即告诉GC不需要再调用该对象的析构方法,否则,GC仍会在判断该对象不再有用后调用其析构方法,虽然程序不会出错,但影响系统性能。
  • 析构函数 和 Dispose 释放的资源应该相同,这样即使类使用者在没有调用 Dispose 的情况下,资源也会在 Finalize 中得到释放。
  • Finalize 不应为 public。
  • 通过系统GC频繁的调用析构方法来释放资源会降低系统性能,所以推荐显示调用Dispose方法。
  • 有 Dispose 方法存在时,应该调用它,因为 Finalize 释放资源通常是很慢的。

Close函数的说明

 Close 这个方法在不同的类中有不同的含义,并没有任何规定要求 Close 具有特殊的含义,也就是说 Close 并不一定要释放资源,您也可以让 Close 方法表示“关门”。  不过,由于 Close 有“关”的意思,通常也把 Close 拿来释放资源,这也是允许的。比如文件操作中,用 Close 释放对象似乎比 Dispose 含义更准确,于是在设计类时,可以将 Close 设为 public,将 Dispose 设为 protected,然后由 Close 调用 Dispose。 也就是说 Close 表示什么意思,它会不会释放资源,完全由类设计者决定。网上说“Close 调用 Dispose”这种方法是很片面的。在 SqlConnection 中 Close 只是表示关闭数据库连接,并没有释放 SqlConnection 这个对象资源。  、

根据经验,Close 和 Dispose 同时存在的情况下(均为 public),Close 并不表示释放资源,因为通常情况下,类设计者不应该使用两个 public 方法来释放相同的资源。

    3、使用 stackalloc 在栈中分配内存
    C# 提供了stackalloc 关键字可以直接在栈中分配内存,一般情况下,使用栈内存会比使用堆内存速度快,且栈内存不用担心内存泄漏。下面是例子:

int* p = stackalloc int[10];    
for (int i = 0; i < 10; i++)
{
    p[i] = 2 * i + 2;
}
MessageBox.Show(p[9].ToString());

猜你喜欢

转载自blog.csdn.net/qq_25439417/article/details/82110528