天天看点

unsafe在C#程序中的使用

1. unsafe在C#程序中的使用场合:

1)实时应用,采用指针来提高性能;

2)引用非.net DLL提供的如C++编写的外部函数,需要指针来传递该函数;

3)调试,用以检测程序在运行过程中的内存使用状况。

2. 使用unsafe的利弊

好处是:性能和灵活性提高;可以调用其他dll的函数,提高了兼容性;可以得到内存地址;

带来麻烦是:非法修改了某些变量;内存泄漏。

3. unsafe的使用

unsafe可以用来修饰类、类的成员函数、类的全局变量,但不能用来修饰类成员函数内的局部变量。编译带有unsafe代码的程序也要在“configuration properties>build” 中把允许unsafe代码设为真。

但是在managed code中使用unsafe时也要注意,正因为CLR可以操作内存对象,假如你写了一下代码:

public unsafe void add(int *p)

{

*p=*p+4;

}

p的地址值可能会在运行过程中被CLR所修改,这通常可采用fixed来处理,使指针所指向的地址不能被改变。如下:

fixed(int *p=& value)

{

add(p);

}

(*) unsafe 和 fixed

unsafe

{               

     int [] array  =   new   int [ 10 ];

     for  ( int  i  =   0 ; i  <  array.Length; i ++ )

    {

        array[i]  =  i;

    }

     fixed  ( int *  p  =  array) 

    {

         for  ( int  i  =   0 ; i  <  array.Length; i ++ )

        {

            System.Console.WriteLine(p[i]);

        }                    

    }               

}

指针在c#中是不提倡使用的,有关指针的操作被认为是不安全的(unsafe)。因此运行这段代码之前,先要改一个地方,否则编译不过无法运行。

修改方法:在右侧的solution Explorer中找到你的项目,在项目图标(绿色)上点右键,选最后一项properties,然后在Build标签页里把Allow unsafe code勾选上。之后这段代码就可以运行了,你会看到,上面这段代码可以像C语言那样用指针操纵数组。但前提是必须有fixed (int* p = array),它的意思是让p固定指向数组array,不允许改动。因为C#的自动垃圾回收机制会允许已经分配的内存在运行时进行位置调整,如果那样,p可能一开始指的是array,但后来array的位置被调整到别的位置后,p指向的就不是array了。所以要加一个fixed关键字,把它定在那里一动不动,之后的操作才有保障。

另有两点需要注意:

1)指针的使用必须放在unsafe的区域里;unsafe关键字也可作为类或方法的修饰符。

2)fixed (int* p = array)中,p的定义不能写在别处,而且fixed关键字也只能在unsafe区域里使用。

(*) 略简洁的unsafe写法 

     class  Program

    {

         unsafe   public   static  UInt16 Htons(UInt16 src) 

        {

            UInt16 dest;

             //  不能照搬C的源代码,因为有些类型长度不一样,如char(2字节),long(8字节)

             //  ((char*)&dest)[0] = ((char*)&src)[1];

             //  ((char*)&dest)[1] = ((char*)&src)[0];

            (( byte * ) & dest)[ 0 ]  =  (( byte * ) & src)[ 1 ];

            (( byte * ) & dest)[ 1 ]  =  (( byte * ) & src)[ 0 ];

             return  dest;

        }

         public   static  UInt16 ConciseHtons(UInt16 src) 

        {

            UInt16 dest;

             unsafe

            {

                (( byte * ) & dest)[ 0 ]  =  (( byte * ) & src)[ 1 ];

                (( byte * ) & dest)[ 1 ]  =  (( byte * ) & src)[ 0 ];

            }            

             return  dest;

        }

         static   void  Main() 

        {

            UInt16 val  =   1 ;

             //  如果方法是unsafe的,则必须在unsafe block里调用

             unsafe

            {                

                val  =  Htons(val);

            }

            Console.WriteLine(val);

             //  更简洁的写法是把unsafe block写在函数内部

            val  =  ConciseHtons(val);

            Console.WriteLine(val);

        }                

    }

(*) stackalloc

stackalloc的用处仅仅是把数组分配在栈上(默认是分配在托管堆上的)。

     class  MyClass

    {

         public   int  val;

    }

     class  Program

    {                

         static   void  Main() 

        {            

             unsafe

            {                

                MyClass  * p  =   stackalloc  MyClass[ 1 ];  //  Error!! 如果类型要放在托管堆上则不行,如果MyClass是struct就OK了

                p -> val  =   1 ;

                 int   * iArray  =   stackalloc   int [ 100 ];   //  OK,在栈上创建数组, int类型本身就是放在栈上的

            }            

        }                

    }

注意:指针指向的内存一定要固定。凡是C#里的引用类型(一切类型的数组都是引用类型)都是分配在托管堆上的,都不固定。有两种方法强制固定,一种是用stackalloc分配在栈上,另一种是用fixed分配在堆上。

继续阅读