為了保持類型安 全,默認情況下,C# 不支持指針算法。 不過,通過使用 unsafe 關鍵字,可以定義可使用指針的不安全上下文。
?
unsafe 在C# 程 序中的使用場合:
1)實時應用,采用指針來提高性能;
2)引用非.net DLL提供的如C++編寫的外部函數,需要指針來傳遞該函數;
3)調試,用以檢測程序在運行過程中的內存使用狀況。
使用unsafe 的利弊:
好處:性能和靈活性提高;可以調用其他dll的函數,提高了兼容性;可以得到內存地址;
壞處:非法修改了某些變量;內存泄漏。
unsafe 與unmanaged的區別:
managed code是在CLR監管下運行的程序。以下任務由CLR來執行:管理對象內存,類型安全檢測和冗余處理。
unmanaged code也就是能由程序員直接進行內存操作的程序。
unsafe 是介于managed和unmanaged之間的橋梁,它使得managed code也能使用指針來控制和操作內存。
?
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);
??????? }
?
托管代碼 (managed code):由公共語言運行庫環境(而不是直接由操作系統)執行的代碼。托管代碼應用程序可以獲得公共語言運行庫服務,例如自動
垃圾回收、運行庫類型檢查和安全支持等。這些服務幫助提供獨立于平臺和語言的、統一的托管代碼應用程序行為。
非托管代碼(Unmanaged Code):在公共語言運行庫環境的外部,由操作系統直接執行的代碼。非托管代碼必須提供自己的垃圾回收、類型檢查、安全支
持等服務;它與托管代碼不同,后者從公共語言運行庫中獲得這些服務。
Unsafe的代碼介于這兩者之間,它也是在CLR的環境中執行,但是我們可以直接操作內存。只要我們的代碼包含下面三個指針操作符之一就需要使用Unsafe
關鍵字:
*
&
->
例如:
unsafe static void ChangeValue(int* pInt)
{
*pInt = 23;
}
上面的代碼由于是在CLR下托管執行,為 了減少內存碎片C#的自動垃圾回收機制會允許已經分配的內存在運行時進行位置調整,所以如果我們多次調用的話就可能
導致指針指向其他的變量。比如*pInt為
指向一個變量的地址為1001,CLR在重新內存整理分配后該變量 就存儲在地址為5001的地方。而原來1001的地方可能會
被分配其他變量,要解決這個問題我們就需要使用Fixed關鍵字。
fixed 語句禁止垃圾回收器重定位可移動的變量。fixed 語句只能出現在不安全的上下文中。Fixed 還可用于創建固定大小的緩沖區。如下面例子:
using System;
class CaryData
{
public int data;
}
class CProgram
{
unsafe static void ChangeValue(int* pInt)
{
*pInt = 23; //3為這個指針的地址賦值23
}
public unsafe static void Main()
{
CaryData cd = new CaryData();
Console.WriteLine("改變前: {0}", cd.data);
fixed (int* p = &cd.data) // 1把整形的地址賦給了指針P
{
ChangeValue(p); //2專遞指針
}
Console.WriteLine("改變后: {0}", cd.data); //4由于cd.data的和*p地址相同,所以cd.data 的輸出是23
}
}
注意要勾選項目屬性中生成標簽的允許不安全代碼。
?
T_Account ret;
unsafe
{
fixed(void* ptr = body)
{
ret = *((T_Account*)ptr); // 轉換指針為(T_Account*),再獲得指針的值也就是T_Account類型值
}
}
?
T_Account x = new T_Account();
x.ID = 12;
x.Name = "thisistest";
x.Native_Currency = "USD";
byte[] message = new byte[T_Account.Size];
unsafe
{
void* ptr = &x; //將地址賦給這個指針
fixed(void* des = message) // 值賦給了這個地址
{
MemoryUtility.CopyData(des, ptr, T_Account.Size);
}
}
(*) 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 分配在堆上。