C# 内存操作常用函数

来源:互联网 发布:真皮床哪个品牌好 知乎 编辑:程序博客网 时间:2024/05/29 03:23

写这篇文章是有一个起因的...

最近在学socket编程 翻源代码的时候 无意发现一个没有用过的函数Marshal.UnsafeAddrOfPinnedArrayElement()
它的原形如下:

public static IntPtr UnsafeAddrOfPinnedArrayElement(Array arr, int index);

这个静态函数的作用是返回一个数组第index个元素的首地址
而且没有值类型和引用类型的限制 没有数组索引越界的检查
简单的说 它是一个托管的C#数组与非托管指针的一个合法的转换接口

这样 我们就很容易写出以下代码了:

unsafe{    byte[] array = new byte[1024];    byte* pArray = (byte*)Marshal.UnsafeAddrOfPinnedArrayElement(array, 0);    for (int i = 0, j = array.Length; i < j; i++)    {        (pArray[i])++;    }}

他的好处是 在改变数组值的时候没有进行索引越界检查 效率会略微提高

当然 它只在极端需要效率的场合值得一用 或者是密集计算的场合懒得用C++写底层然后dllImport的时候用
它比普通的数组遍历能提高5%~15%的效能
毕竟 指针玩不好终究是个挺危险的东西..

 

另外一个有趣的函数是Buffer.BlockCopy()
原形如下:

public static void BlockCopy(Array src, int srcOffset, Array dst, int dstOffset, int count);

它也是用来复制内存区块的 和Array.Copy()不同 它最后一个参数count是表示复制的字节长度 而不是复制元素的数量
当然 这个函数的源数组和目的数组 类型可以不同 但是必须是基本类型(原始的各种数值型)

大致用法是这样:

double[] src = new double[5] { 1, 2, 3, 4, 5 };byte[] dst = new byte[40];Buffer.BlockCopy(src, 0, dst, 0, 40);

于是它就会按照double的IEEE内存标准 原封不动的复制到byte[]里去了...
事实上用上面提供的数组转指针 以及Marshal.Copy()方法也可以做到 不过这函数方便了很多

 

BitConverter类中提供了一系列静态函数 如GetBytes()和ToInt32()等 可以在基本类型和byte[]间进行转换
它比较适合单一转换的场合 如果需要循环转换的话还是使用BlockCopy或者直接unsafe指针读取更为方便

 

Marshal.Copy()提供了一系列的重载 可以提供托管数组和IntPtr之间的内存复制功能...
于是经常我们会看到一段GDI图像处理代码如下:

Bitmap bitmap = (Bitmap)Bitmap.FromFile(fileName);BitmapData data = bitmap.LockBits(new Rectangle(new Point(), bitmap.Size), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);byte[] buffer = new byte[data.Stride * data.Height];Marshal.Copy(data.Scan0, buffer, 0, buffer.Length);for (int h = 0; h < data.Height; h++){    for (int w = 0; w < data.Width; w++)    {        //...    }}Marshal.Copy(buffer, 0, data.Scan0, buffer.Length);bitmap.UnlockBits(data);

事实上 直接使用指针可以在不创建缓冲区的情况下对非托管数据进行操作
如果是对一定批量的非托管数据进行处理 可以使用缓冲区(如上面的情况可以图像逐行复制处理)
或者直接使用unsafe代码 更为高效

 

另外一件比较讨厌的事 Marshal.Copy()没有提供从指针到指针复制的函数重载 不过我们可以用API来实现它:

[DllImport("kernel32.dll")]public static extern void CopyMemory(IntPtr Destination, IntPtr Source, int Length);

IntPtr当然也可以使用void*来替代或者重载 以达到更好的灵活性
结合数组取地址的方式 这个函数可以提供不同类型的 非基础类型数组之间的复制

 

总之 因为C#的特殊支持 可以很轻松的直接处理内存 写出类似C++的指针运算代码
在保证足够安全性的前提下  它会带来意想不到的语法便利 以及高效的处理速率

原创粉丝点击