C#中指针的使用
http://www.cnblogs.com/caibq/archive/2011/09/11/Cai_Yao.html
在C#中,有时候希望通过指针来操作内存,这样可以提高效率。我们可以用unsafe关键字修饰含有指针操作的程序段,如下所示:
class Program
{
static int Main(string[] args)
{
unsafe
{
// ...
}
}
}
unsafe只是编译器指令语法,只是在编译的时候起作用,并不是说unsafe修饰的程序段是unmanaged,他们仍然是managed的。
在C#里定义指针和C++有些不同:
1. C#指针定义的星号只能和类型在一起
int* p1, p2; //p1和p2都是int*指针
而在C++中与之等价的定义是:
int *p1, *p2; //C++
int *p1, p2; //C++中,p2只是一个普通的int
2. C#指针类型不能是引用类型,不能是含有引用类型的自定义类型或复合类型,只能是预定义类型
string* pMessage //错误C#指针定义
struct ServiceStatus
{
int State;
string Description; // 引用类型
}
ServiceStatus* pStatus //错误指针定义
3. C#中指针赋值
在C#里,引用类型也叫Movable类型,由于垃圾回收器和内存布局优化,在堆上分配的内存是不固定的,会被挪动。所不
能将堆上分配的地址赋值给C#指针, 如果能将堆上地址固定,就没有问题了,那么如何固定引用类型?C#中有一个关键字
fixed可以用在此处。
byte* pData = &bytes[0]; // 编译错误,数组在C#里是引用类型,属于movable
byte[] bytes = new byte[24];
fixed (byte* pData = &bytes) //正确,使用fixed固定
{
// ...
}
当然,使用fixed不是没有代价的,它会使内存出现碎片。
========
C# 指针之美
http://www.cnblogs.com/xiaotie/archive/2012/07/06/2579853.html
将C#图像库的基础部分开源了(https://github.com/xiaotie/GebImage)。这个库比较简单,且离成熟还有一段距离,但它是一种新的开发模式的探索:以指针和非托管内存为主的C#程序开发。
我许多项目都是在这个库基础上的开发,实战证明了它的有效。从今天起,将断断续续(太忙了)的写一系列文章来介绍这种开发方法,介绍基于此的图像编程。本文便是第一篇。
以指针和非托管内存为主的C#程序开发,无论对.Net程序员来说,还是对传统的C/C++程序员来说,均属异类。然而这种方法在很多场景下是非常有效的,尤其是图像编程,所谓谈笑间,樯橹灰飞烟灭,不外如是。
既有C/C++的高性能,又能直接管理内存不给GC带来压力,同时又拥有.net开发的大部分优势,可以快速迭代,何乐而不为呢?
一、简洁优美的代码
本来初稿这节写了好几百字,将C#指针开发与C/C++开发,Java开发、D语言开发等进行对比,阐述理念。不过现在觉得,阐述一个新事物,没有比用例子更直接的了。
例子:打开一张图像,先将它转化为灰度图像,再进行二值化(变成黑白图像),然后进行染色,将白色的像素变成红色。以上每一个过程都弹出窗体显示出来。
代码截图更有视觉冲击力:
像诗歌一样简洁和优美,这就是孤的代码。具备C/C++的高性能和C#的行云流水,同时又有IDE的强大生产力相助,说这些话已属多余,看见这样的代码,更应该想到的是:妹纸,今天工作全部搞定,现在有空吗,哥来接你。
这才是工作,这才是生活。留下时间,看看书,看看漫画,玩玩乐乐。最近在看《偷星九月天》,就拿沧殿来测试这段程序吧:
改编
程序员A:那帮孙子又新提了几百条需求,老大,你要带我们突围吗?
程序员B:不是突围,是杀光它们!
下面,请跟随我,来一段短程探险吧。
(本文中的代码可在 https://github.com/xiaotie/GebImage/tree/develop 处下载。打包下载地址见 集异璧图像与视觉分析库 )
二、C# 指针基础
在C#中使用指针,需要在项目属性中选中“Allow unsafe code”:
接着,还需要在使用指针的代码的上下文中使用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;
}
}
}
指针可以操作栈上的值类型,如:
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,自己管理内存了。
三、几种常用用法
1、使用Dispose模式管理非托管内存
如果使用非托管内存,建议用Dispose模式来管理内存,这样做有以下好处: 可以手动dispose来释放内存;可以使用using 关键字开管理内存;即使不释放,当Dispose对象被GC回收时,也会收回内存。
下面是Dispose模式的简单例子:
View Code
使用:
using (UnmanagedMemory memory = new UnmanagedMemory(10))
{
int* p = (int*)memory.Handle;
*p = 20;
MessageBox.Show(p->ToString());
}
2、使用 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());
3、模拟C中的union(联合体)类型
使用 StructLayout 可以模拟C中的union:
[StructLayout(LayoutKind.Explicit)]
public struct Argb32
{
[FieldOffset(0)]
public Byte Blue;
[FieldOffset(1)]
public Byte Green;
[FieldOffset(2)]
public Byte Red;
[FieldOffset(3)]
public Byte Alpha;
[FieldOffset(0)]
public Int32 IntVal;
}
这个和指针无关,非unsafe环境下也可使用,有很多用途,比如,序列化和反序列化,求hash值 ……
四、C# 指针操作的几个缺点
C# 指针操作的缺点也不少。下面一一道来。
缺点1:只能用来操作值类型
.Net中,引用类型的内存管理全部是由GC代劳,无法取得其地址,因此,无法用指针来操作引用类型。所以,C#中指针操作受到值类型的限制,其中,最主要的一点就是:值类型无法继承。
这一点看起来是致命的,其实不然。首先,需要用到指针来提高性能的地方,其类型是很少变动的。其次,在OO编程中有个名言:组合优于继承。使用组合,我们可以解决很多需要继承的地方。第三,最后,我们还可以使用引用类型来对值类型打包,进行继承,权衡两者的比重来完成任务。
缺点2:泛型不支持指针类型
C# 中泛型不支持指针类型。这是个很大的限制,在后面的篇幅中,我会引入模板机制来克服这个问题。同理,迭代器也不支持指针,因此,我们需要自己实现迭代机制。
缺点3:没有函数指针
幸运的是,C# 中有delegate,delegate 支持支持指针类型,lambda 表达式也支持指针。后面会详细讲解。
五、引入模板机制
没有泛型,但是我们可以模拟出一套类似C++的模板机制出来,进行代码复用。这里大量的用到了C#的语法糖和IDE的支持。
先介绍原理:
partial 关键字让我们可以将一个类的代码分在多个文件,那么可以这样分:第一个文件是我们自己写的代码,第二个文件用来描述模板,第三个文件,用来根据模板自动生成代码。
三个文件这样取名字的:
XXXClassHelper 是模板定义文件,XXXClassHelper_Csmacro.cs 是自动生成的模板实现代码。
ClassHelper文件的例子:
namespace Geb.Image
{
using TPixel = Argb32;
using TCache = System.Int32;
using TKernel = System.Int32;
using TImage = Geb.Image.ImageArgb32;
using TChannel = System.Byte;
public static partial class ImageArgb32ClassHelper
{
#region include "ImageClassHelper_Template.cs"
#endregion
}
public partial class ImageArgb32
{
#region include "Image_Template.cs"
#endregion
#region include "Image_Paramid_Argb_Templete.cs"
#endregion
}
public partial struct Argb32
{
#region include "TPixel_Template.cs"
#endregion
}
}
这里用到了using 语法糖。using 关键字,可以为一个类型取别名。使用 VS 的 #region 来定义所使用的模板文件的位置。上面这个文件中,引用了4个模板文件:ImageClassHelper_Template.cs,Image_Template.cs,Image_Paramid_Argb_Templete.cs 和 TPixel_Template.cs。
只看其中的一个模板文件 Image_Template.cs:
using TPixel = System.Byte;
using TCache = System.Int32;
using TKernel = System.Int32;
using System;
using System.Collections.Generic;
using System.Text;
namespace Geb.Image.Hidden
{
public abstract class Image_Template : UnmanagedImage
{
private Image_Template()
: base(1,1)
{
throw new NotImplementedException();
}
#region mixin
public unsafe TPixel* Start { get { return (TPixel*)this.StartIntPtr; } }
public unsafe TPixel this[int index]
{
get
{
return Start[index];
}
set
{
Start[index] = value;
}
}
……
#endregion
}
}
这个模板文件是编译通过的。也使用了 using 关键字来对使用的类型取别名,同时,在代码中,有一段用 #region mixin 和 #endregion 环绕的代码。只需要写一个工具,将模板文件中 #region mixin 和 #endregion 环绕的代码提取出来,替换到模板定义中 #region include "Image_Template.cs" 和 #endregion 之间,生成第三个文件 ClassHelper_Csmacro.cs 即可实现模板机制。由于都使用了 using 关键字对类型取别名,因此,ClassHelper_Csmacro.cs 文件也是可以编译通过的。在不同的模板定义中,令同样的符号来代表不同的类型,实现了模板代码的公用。
上面机制可以全部自动化。Csmacro 是我写的一个工具,可以完成上面的过程。将它放在系统路径下,然后在项目的build event中添加pre-build 指令即可。Csmacro 程序在代码包的lib的目录下。
如此实装,我们就有模板用了!一切自动化,就好像内置的一样。强类型、有编译器进行类型约束,减少出错的可能。调试也很容易,就和调试普通的C#代码一样,不存在C++中的模板的难调试问题。缺点嘛,就是没有C++中模板的语法优美,但是,也看的过去,至少比C中的宏好看多了是吧。
参照上面对模板的实现,完全可以定义出一套C#的宏出来。没这样做,是因为没这个需求。
下面是一个完整的例子,为 Person 类和 Cat 类添加模板扩展方法(非扩展方法也可类似添加),由于这个方法有指针,无法用泛型实现:
void SetAge(this T item, int* age)
首先,建一个可编译通过的模板类 Template.cs:
namespace Introduce.Hide
{
using T = Person;
public static class Template
{
#region mixin
public static unsafe void SetAge(this T item, int* age)
{
item.Age = *age;
}
#endregion
}
}
我在命名空间中加入了 Hide,只要不引用这个命名空间,这个扩展方法不会出现对程序产生干扰。
接着,建立 PersonClassHelper.cs 文件:
namespace Introduce
{
using T = Person;
public static partial class PersonClassHelper
{
#region include "Template.cs"
#endregion
}
}
建立 CatClassHelper.cs 文件:
namespace Introduce
{
using T = Cat;
public static partial class CatClassHelper
{
#region include "Template.cs"
#endregion
}
}
为了节省篇幅,我省略了命名空间的引用,实际代码中是有命名空间的引用的。下载包里包含了全部的代码。
接下来,编译一下,哈哈,编译通过。
且慢,怎么看不到编译生成的两个 Csmacro.cs 文件呢?
这两个文件已经生成了,需要手动将它们添加到项目中,只用添加一次即可。添加进来,再编译一下,哈哈,通过。
这个例子虽小,可不要小看模板啊,在Geb.Image库里,大量使用了模板:
有了模板,只用维护公共代码。
六、迭代器
下面来实现迭代器。这里,要放弃使用foreach,返回古老的迭代器模式,来访问图像的每一个像素:
public unsafe struct ItArgb32Old
{
public unsafe Argb32* Current;
public unsafe Argb32* End;
public unsafe Argb32* Next()
{
if (Current < End) return Current ++;
else return null;
}
}
public static class ImageArgb32Helper
{
public unsafe static ItArgb32Old CreateItorOld(this ImageArgb32 img)
{
ItArgb32Old itor = new ItArgb32Old();
itor.Current = img.Start;
itor.End = img.Start + img.Length;
return itor;
}
}
不幸的是,测试性能,这个迭代器比单纯的while循环慢很多。对一个100万像素的图像,将其每一个像素值的Red分量设为200,循环100遍,使用迭代器在我的电脑上耗时242 ms,直接使用循环耗时 72 ms。我测试了很多种方案,均未得到和直接循环性能近似的迭代器实现方案。
没有办法,只好对迭代器来打折了,只进行部分抽象(这已经不能算迭代器了,但这里仍沿用这个名称):
public unsafe struct ItArgb32
{
public unsafe Argb32* Start;
public unsafe Argb32* End;
public int Step(Argb32* ptr)
{
return 1;
}
}
产生迭代器的代码:
public unsafe static ItArgb32 CreateItor(this ImageArgb32 img)
{
ItArgb32 itor = new ItArgb32();
itor.Start = img.Start;
itor.End = img.Start + img.Length;
return itor;
}
使用:
ItArgb32 itor = img.CreateItor();
for (Argb32* p = itor.Start; p < itor.End; p+= itor.Step(p))
{
p->Red = 200;
}
测试性能和直接循环性能几乎一样。有人可能要问,你这样有什么优势?和for循环有什么区别?
这个例子中当然看不出优势,换个例子就可以看出来了。
在图像编程中,有 ROI(Region of Interest,感兴趣区域)的概念。比如,在下面这张女王出场的画面中,假设我们只对她的头部感兴趣(ROI区域),只对该区域进行处理(标注为红色区域)。
对ROI区域创建一个迭代器,用来迭代ROI中的每一行:
public unsafe struct ItRoiArgb32
{
public unsafe Argb32* Start;
public unsafe Argb32* End;
public int Width;
public int RoiWidth;
public int Step(Argb32* ptr)
{
return Width;
}
public ItArgb32 Itor(Argb32* p)
{
ItArgb32 it = new ItArgb32();
it.Start = p;
it.End = p + RoiWidth;
return it;
}
}
这个ROI迭代器又可以产生一个ItArgb32迭代器,来迭代该行中的像素。
产生ROI迭代器的代码如下,为了简化代码,我这里没有进行ROI的验证:
public unsafe static ItRoiArgb32 CreateRoiItor(this ImageArgb32 img,
int x, int y, int roiWidth, int roiHeight)
{
ItRoiArgb32 itor = new ItRoiArgb32();
itor.Width = img.Width;
itor.RoiWidth = roiWidth;
itor.Start = img.Start + img.Width * y + x;
itor.End = itor.Start + img.Width * roiHeight;
return itor;
}
性能测试表明,使用ROI迭代器进行迭代和直接进行循环,性能一致。
为一副图像添加ROI字段,设置ROI值来控制不同的处理区域,然后用ROI迭代器进行迭代,比直接使用循环要方便得多。
七、风情万种的Lambda表达式
接下来,来看看C#指针最有风情的一面——Lambda表达式。
C# 里 delegate 支持指针,下面这种写法是没有问题的:
void ActionOnPixel(TPixel* p);
对于图像处理,我定义了许多扩展方法,ForEach是其中的一种,下面是它的模板定义:
public unsafe static UnmanagedImage ForEach(this UnmanagedImage src, ActionOnPixel handler)
{
TPixel* start = (TPixel*)src.StartIntPtr;
TPixel* end = start + src.Length;
while (start != end)
{
handler(start);
++start;
}
return src;
}
让我们用lambda表达式对图像迭代,将每像素的Red分量设为200吧,一行代码搞定:
img.ForEach((Argb32* p) => { p->Red = 200; });
用ForEach测试,对100万像素的图像设置Red通道值为200,循环100次,我的测试结果是 400 ms,约是直接循环的 4-5 倍。可见这是个性能不高的操作(其实也够高了,100万象素,循环100遍,耗时400ms),可以在对性能要求不是特别高时使用。
八、与C/C++的比较
我测试了很多场景,C# 下指针性能约是 C/C++ 的 70-80%,性能差距,可以忽略。
相对于C/C++来说,C#无法直接操作硬件是其遗憾,这种情况,可以使用C/C++写段小程序来弥补,不过,我还没遇到这种场景。很多情况都可以P/Invoke解决。
做图像的话,很多时候需要使用显卡加速,如使用CUDA或OpenCL,幸运的是,C#也可以直接写CUDA或OpenCL代码,但是功能可能会受到所用的库的限制。也可以用传统方式写CUDA或OpenCL代码,再P/Invoke调用。如果用传统的C/C++开发的话,也需要做同样的工作。
和C比较:
这套方案比C的抽象程度高,我们有模板,有lambda表达式,还有一大票的语法糖。在类库上,比C的类库完善的多。我们还有反射,有命名空间等等一大票的东西。
和C++比较:
这套方案的抽象程度比C++要低一些。毕竟,值类型无法继承,模板机制比C++ 差一点。但是在生产力上比C++要高很多。抛开C++那一大票陷阱不说,以秒计算的编译速度就够让C++程序员流口水的。当我们在咖啡馆里约会喝咖啡时,C++程序员还正端着一杯咖啡坐在电脑前等待程序编译结束。
九、接下来的工作
接下来的工作主要有两个:
内联工具:C# 的内联还不够强大。需要一个内联工具,对想要内联的方法使用特性标记一下,在编译结束后,在IL代码层面内联。
翻译工具:移动开发是个痛。如何将C#的代码翻译成C/C++的代码,在缺乏.Net的运行时下运行?
这两个工作都不紧要。C#内联效果不好的地方(这种情况很少),可以手动内联。至于移动开发嘛,在哥的一云三端大计中,C# 的定位是云图像开发(C#+CUDA),三端中,桌面运用是用C#和Flash开发,Web和移动应用使用Flash开发,没有C#的事情。
C/C++ 呢?更没有它们的位置啦!不对,还是有的。用它们来开发Flash应用的核心算法!够另类吧!
========
在c#中使用指针
http://blog.csdn.net/susan19890313/article/details/7365996
版权声明:本文为博主原创文章,未经博主允许不得转载。
如果想在c#中使用指针,首先对项目进行配置:在解决方案资源管理器中右击项目名选择属性(或在项目菜单中选择consoleApplication属性(consoleApplication为项名)),在生成选项卡中 选中“允许不安全代码”,如下图:
然后将有关指针,地址的操作放在unsafe语句块中。使用unsafe关键字是来告诉编译器下面的代码是不安全的。
unsafe关键字的使用:
(1)放在函数前,修饰函数,说明在函数内部或函数的形参涉及到指针操作
unsafe static void FastCopy(byte[] src, byte[] dst, int count)
{
// Unsafe context: can use pointers here.
}
不安全上下文的范围从参数列表扩展到方法的结尾,因此指针作为函数的参数时须使用unsafe关键字
unsafe static void FastCopy ( byte* ps, byte* pd, int count ) {...}
(2)将有关指针的操作放在由unsafe声明的不安全块中
unsafe
{
// Unsafe context: can use pointers here.
}
示例:
[csharp] view plain copy
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
int i = 1;
unsafe
{
Increment(&i); //将函数调用放在unsafe中是因为实参是指针类型
}
Console.WriteLine(i+"\n");
//演示如何使用指针来操作字符串
string s = "Code Project is cool";
Console.Write("the original string : ");
Console.WriteLine("{0}\n", s);
char[] b = new char[100];
s.CopyTo(0, b, 0, 20);//public void CopyTo (int sourceIndex,char[] destination,int destinationIndex,int count)
//将指定数目的字符从此实例中的指定位置复制到 Unicode 字符数组中的指定位置。
Console.Write("the encoded string : ");
unsafe
{
fixed (char* p = b)
{
NEncodeDecode(p);
}
}
for (int t = 0; t < 20; t++)
Console.Write(b[t]);
Console.WriteLine("\n");
Console.Write("the decoded string : ");
unsafe
{
fixed (char* p = b)
{
NEncodeDecode(p);
}
}
for (int t = 0; t < 20; t++)
Console.Write(b[t]);
Console.WriteLine();
}
unsafe public static void Increment(int* p)
{
*p = *p + 1;
}
unsafe public static void NEncodeDecode(char* s)
{//将一段字符串通过异或运算进行编码解码的操作。如果您将一段字符串送入这个函数这个字符串将会被编码,
//如果您将一段已经编码的字符送入这个函数,这段字符串将会被解码。
int w;
for (int y = 0; y < 20; y++)
{
w = (int)*(s + y);
w = w ^ 5;//^为按位异或。
*(s + y) = (char)w;
}
}
}
}
输出结果:
关键字fixed简介:
fixed语句只能出现在不安全的上下文中。
C# 编译器只允许在 fixed 语句中分配指向托管变量的指针,无法修改在 fixed 语句中初始化的指针
fixed 语句禁止垃圾回收器重定位可移动的变量。 当你在语句或函数之前使用fixed时,你是在告诉.Net平台的垃圾回收器,在这个语句或函数执行完毕前,不得回收其所占的内存空间。
示例:
(1)
// assume class Point { public int x, y; } pt is a managed variable, subject to garbage collection.
Point pt = new Point();
// Using fixed allows the address of pt members to be taken, and "pins" pt so it isn't relocated.(pin v 固定住)
fixed ( int* p = &pt.x )
{
*p = 1;
}
(2)用数组或字符串的地址初始化指针:
fixed (int* p = arr) ... // equivalent to p = &arr[0]
fixed (char* p = str) ... // equivalent to p = &str[0]
(3)只要指针的类型相同,就可以初始化多个指针:
fixed (byte* ps = srcarray,pd = dstarray) {...}
要初始化不同类型的指针,只需嵌套 fixed 语句:
fixed (int* p1 = &p.x)
{
fixed (double* p2 = &array[5])
{
// Do something with p1 and p2.
}
}
========
C#中指针使用总结
http://www.cnblogs.com/jasonlny/p/3203439.html
C#为了类型安全,默认并不支持指针。但是也并不是说C#不支持指针,我们可以使用unsafe关键词,开启不安全代码(unsafe code)开发模式。在不安全模式下,我们可以直接操作内存,这样就可以使用指针了。在不安全模式下,CLR并不检测unsafe代码的安全,而是直接执行代码。unsafe代码的安全需要开发人员自行检测。
一、Vs2010中开启unsafe code 的方式
在方法、类、代码块中使用unsafe关键词,如:
unsafe static void Main(string[] args){ //代码}
unsafe
{
//代码块
}
然后再项目上点击鼠标右键,选择“属性”,在“生成”选项卡中选中“允许不安全代码”
二、C#可以定义为指针的类型有
sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, bool,
struct(结构体),结构体中只能包括非托管类型。
三、C#指针操作符
操作符
说明
*
取值运算符
&
取址运算符
->
通过指针处理结构体中的数据(获取或赋值)
++与–
指针增、减操作
fixed
用户暂时固定托管代码中引用类型的位置。
Stackallc
分配内存
例如分配内存
char* cptr = stackalloc char[26];
for (int i = 0; i < 26;i++ )
{
cptr[i] = (char) (i+65);
}
for (int i = 0; i < 26;i++ )
{
Console.WriteLine(string.Format("{0}:{1}",(int)&cptr[i],cptr[i]));
}
至于释放内存,我在msdn上搜索了一下,c#并没有提供释放内存的函数。而msdn给的解释是:分配的内存会在方法结束后自动释放。
fixed的应用会在下面的类与指针中做说明。
四、C#指针的定义
定义指针
说明
int* p
整形指针
int** p
指向整形指针的指针
char* c
指向字符的指针
int*[] arr
整形一维数组指针
五、指针的使用
1.整形指针的使用
int i=10;
int* iptr = &i; //将i的地址赋值给iptr
Console.WriteLine((int)&iptr); //取iptr得地址
Console.WriteLine(*iptr); //取iptr指向的值
2.结构体指针
struct Location
{
public int X;
public int Y;
}
unsafe static void Main(string[] args)
{
Location location;
location.X = 10;
location.Y = 5;
Location* lptr = &location;
Console.WriteLine(string.Format("location 地址{0},lptr地址{1},lptr值{2}",(int)&location,(int)lptr,*lptr));
Console.WriteLine(string.Format("location.x的地址{0},location.x的值{1}",(int)&(lptr->X),lptr->X));
Console.WriteLine(string.Format("location.y的地址{0},location.y的值{1}", (int)&(lptr->Y), lptr->Y));
}
以上代码输出结构体的地址和值。我们在操作地址时,可以直接看到结构体的内存分配。
3.指针与参数
public static unsafe int* Add(int* x,int* y)
{
int sum = *x + *y;
return ∑
}
int i = 2, j = 3;
Console.WriteLine(*Add(&i,&j));
4.类与指针,因为类是托管类型,我们知道类受到“垃圾收集”的影响,它的内存地址是不固定的。而且类是引用类型,是不能声明为指针类型的。而指针分配内存后,不受“垃圾收集”影响,地址是固定的。所以为了使用类中的数据,我们需要临时固定类的地址。这就用到fixed关键词,用fixed后,就可以操作类中的值类型了。
class People
{
public int Age; //值类型,不可以是属性
public void ShowAge()
{
Console.WriteLine(Age);
}
}
People people = new People();
people.Age = 10;
fixed(int* agePtr=&people.Age)
{
*agePtr += 1;
}
people.ShowAge(); //11
通过以上的方法,我们可以操作值类型,也可以获得值类型的地址。但如何获取类的内存地址?我们可以使用GCHandle,来自System.Runtime.InteropServices命名空间。GCHandle提供从非托管内存访问托管对象的方法。如下:
object p = new People();
GCHandle h = GCHandle.Alloc(p, GCHandleType.Pinned);
IntPtr addr = h.AddrOfPinnedObject();
Console.WriteLine(addr.ToString());
h.Free();
六、C#中使用指针的总结
1.引用类型不能定义为指针
2.msdn上说enum可以定义为指针,可是我真不知道它的用处是什么。所以在指针的类型中并没有出现enum类型。
3.c#中的指针操作远远不如c/c++,如果想学习指针的话,还是用c/c++
4.微软并不推荐使用unsafe code模式,也不推荐使用指针。在msdn官方文档中,唯一一句赞美C#指针的话就是“合理的使用指针,可以提高程序的执行速度”。但是什么是“合理的使用”?我下载了msdn上的几个关于C#指针的实例代码,发现用的最多的是调用api函数,在api函数中,有大量的指针参数。
5.fixed的使用可能产生存储碎片,因为它们不能移动。如果确实需要固定对象,固定对象的时间应该越短越好。
6.可以使我们了解非托管类型的内存分配。
========
对于.Net中C#指针的研究
在C/C++中,对于指针的使用是很普遍的,可以这么说,如果没有指针的运用,都不知道程序如何来写。在.Net中,同样也是可以使用指针的,不过必须通过开启不安全的代码来使用。在默认情况下,新建的项目是不允许使用不安全的代码,这样,就必须通过设置项目来开启使用,通过设置项目“属性”的“生成”来达到:
image
勾选“允许不安全代码”的选项就OK了。
1、unsafe
要使用指针,还不必须在你的方法体或者是某个作用域中添加unsafe关键字,如:
unsafe static void Main(string[] args)
{
// 不安全代码编写
}
或者:
unsafe
{
// 不安全代码编写
}
2、stackalloc
关键字stackalloc是用于申请栈内存。stackalloc类似于C库中的_alloca。通过使用stackalloc可以自动启用CLR中的缓冲区溢出检测功能。当函数执行完毕后,内存会被自动回收。
使用它的目的通过它来实现对栈内存的使用,从而减少对堆的使用,减少系统开销。
如下参考程序:
int* array = stackalloc int[10];
for (int index = 0; index < 10; index++)
{
array[index] = index;
}
for (int index = 0; index < 10; index++)
{
Console.WriteLine(array[index].ToString());
}
代替了程序:int[] array = new int[10]; 等同于System.Array的数据存在堆中,这里通过指针的方式减少系统的开销。
3、IntPtr 和 ref
IntPtr是.Net提供的托管指针,ref是C#语言中的关键字,和IntPtr没有关系,尽管它们使用起来很类似。区别在于,使用ref可以引用Class,共同点它们都可以引用值类型,包括Struct结构体等等。
接着,编写一个Struct结构体:
[StructLayout(LayoutKind.Sequential)]
public struct ST_TERMPARA
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
public byte[] szAcqID_n_9F01; /**<(TERM)收单行标识*/
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)]
public byte[] szAddTermCap_b_9F40; /**<(TERM)附加终端性能*/
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
public
========