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配置設定在堆上。