C#编写不安全代码 和应用程序域
这篇文章的原文大家可以在 http://www.codeproject.com/csharp/unsafe_prog.asp 找到,作者是Kumar Gaurav Khanna,我仅仅对原文进行翻译,以下是文章的正文
在运行库的控制下执行的代码称作托管代码。相反,在运行库之外运行的代码称作非托管代码。COM 组件、ActiveX 接口和 Win32 API 函数都是非托管代码的示例。
有一个特别的主题始终吸引着最多的C/C++程序员,并且它总被认为太过复杂和困难而难以理解:指针!
然而,当讨论C#时,我遇见的大多数人都持有一种观点,认为在C#中没有指针的概念(如果允许我补充的话,他们的这种观点还非常牢固)。事实上,C#是“抹掉”了这个概念。尽管如此,C#编程中的不安全代码讨论的全是关于编程中指针方面的内容。并非如它字面上的意思那样,在编程中使用指针并没有什么不安全。
它之所以被这样称作不安全代码,是因为它不同于常规的.NET开发,不安全代码编程对程序员有特别的要求。在这篇文章中,我将先从两个很容易混淆的概念开始讨论,不安全代码及托管代码。然后将讨论如何编写不安全代码,也就是如何在C#中使用指针。
不安全代码 还是 非托管代码?这是一个问题
托管代码是指在CLR运行环境的监控下运行的代码。CLR运行环境将负责处理各种“家务”工作,比如:
#管理对象的内存
#执行类型检查
#进行内存垃圾回收
以上只提到了一部分。用户不用自己处理上面提到的工作。用户不用自己去直接操作内存,因为CLR运行环境将处理这些问题。
另一方面,所谓非托管代码是指在CLR环境以外运行的代码。这个概念最好的例子就是我们传统的WIN 32 DLL例如Kernel32.dll , user32.dll 和 安装在我们系统上的COM组件。如何为其分配内存空间,如何释放内存,怎么(如果需要)进行类型检测这些工作由自己来做。典型的C++编程中内存分配指针指向也是非托管代码的另一例子,因为你作为程序员需要自己来负责处理这些工作:
#调用内存分配函数
#确保生成的正确性
#确保当任务结束时候内存被释放
如果你留意的话,你会发现正如上面所说的那样,这些"家务"工作都由CLR运行环境处理,从而将程序员从这些繁重的工作中解放出来.
不安全代码是托管代码与非托管代码之间的纽带
不安全代码在CLR托管环境的监管下运行,就像托管代码那样,但允许你通过使用指针直接访问内存,就像非托管代码中的做法那样.这样,你同时获得了两个世界里最好的东西.你也许要写的程序需要使用传统WIN 32 DLL中的函数,这些函数需要使用指针.这个时候就轮到不安全代码来帮助你了.
现在,我们已经讨论了区别,让我们开始编码吧...毫无疑问,这是最棒的部分,你认为呢?
不安全代码内部
编写不安全代码需要使用两个特殊的关键字:unsafe 和 fixed .我们回忆会知道,总共由3种指针的操作运算:
*
&
->
在任意的语句,代码段,或函数中使用上面的的指针操作运算符,通过使用unsafe 关键字标识为不安全代码,如下面的例子所示:
public unsafe void Triple(int *pInt)
{
*pInt=(*pInt)*3;
}
上面的函数做的事情是将变量的值乘以3然后再对其赋值.值得注意的是包含需要乘以3的变量的地址的在函数中的使用.函数完成其工作.因为函数使用了"*"指针运算符,因此函数被标记为不安全代码,内存被直接的操作.
但是,还有一个问题.回忆我们之前的讨论,不安全代码是托管代码,因此将在CLR托管环境的监管下运行.现在,CLR运行环境可以有权利移动内存中的对象.这是一个可以减少内存碎片的原因.但这样的操作,对程序员来说是不知道的,是对程序员透明的,被指针指向的变量的内存可能被重现安排倒另外的内存位置.(这是由CLR完成的)
因此,如果 *pInt指向的变量的原始地址是1001 , CLR执行了一些内存重新安排以便减少内存碎片后,该变量的地址之前为1001 , 在重新进行内存安排后可能存储在内存中地址为2003的位置.这会是一个大灾难 , 因为指针指向的1001地址什么都没有了,指针变成了无效的.可能这是在.NET下指针使用被弱化的一个原因.你怎么认为呢?
使用固定指针
我们马上来讨论fixed关键字.当在一段代码中使用该关键字时,就告诉了CLR不用去动内存中的那些"问题"对象,它就不会去动它们了.这样,当在C#中使用指针时,使用fixed关键字就很好的避免了在运行时指针无效的问题.让我们来看看它是怎么做的:
using System;
class CData
{
public int x;
}
class CProgram
{
unsafe static void SetVal(int *pInt)
{
*pInt=1979;
}
public unsafe static void Main()
{
CData d = new CData();
Console.WriteLine("Previous value: {0}", d.x);
fixed(int *p=&d.x)
{
SetVal(p);
}
Console.WriteLine("New value: {0}", d.x);
}
}
我们在这里做了这些事情,在"固定"代码段,将CData类的x域的地址赋值给了整形变量指针p.现在,因为CLR被告知当"固定"代码段执行过程中不允许CLR移动其位置,当"固定"代码段的语句在执行时,指针所指向的变量在内存中的位置将不会改变,内存中的变量将不会被CLR重新部署内存位置.
这就是在C#中指针的使用.确保该函数是用unsafe标注的,确保被指向的对象用fixed标注,你也就已经有了在C#中使用指针的能力!