日期:2014-05-20  浏览次数:20918 次

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#中使用指针的能力!