在C#中使用指針的示例代碼
.Net平臺定義了兩種主要數(shù)據(jù)類型:值類型和引用類型,其實還有第三種數(shù)據(jù)類型:指針類型。使用指針,可以繞開CLR的內(nèi)存管理機制。(說明:在C#中使用指針,需要有相關(guān)C/C++指針操作基礎(chǔ))
1、C#中指針相關(guān)的操作符和關(guān)鍵字
| 操作符/關(guān)鍵字 | 作用 |
| * | 該操作符用于創(chuàng)建一個指針變量,和在C/C++中一樣。也可用于指針間接尋址(解除引用) |
| & | 該操作符用于獲取內(nèi)存中變量的地址 |
| -> | 該操作符用于訪問一個由指針表示的類型的字段,和在C++中一樣 |
| [] | 在不安全的上下文中,[]操作符允許我們索引由指針變量指向的位置 |
| ++,-- | 在不安全的上下文中,遞增和遞減操作符可用于指針類型 |
| +,- | 在不安全的上下文中,加減操作符可用于指針類型 |
| ==, !=, <, >, <=, >= | 在不安全的上下文中,比較和相等操作符可用于指針類型 |
| stackalloc | 在不安全的上下文中,stackalloc關(guān)鍵字可用于直接在棧上分配C#數(shù)組,類似CRT中的_alloca函數(shù) |
| fixed | 在不安全的上下文中,fixed關(guān)鍵字可用于臨時固定一個變量以使它的地址可被找到 |
2、在C#中使用指針,需要啟用“允許不安全代碼”設(shè)置
選擇項目屬性->生成,鉤上“允許不安全代碼”

3、unsafe關(guān)鍵字
只有在unsafe所包含的代碼區(qū)塊中,才能使用指針。類似lock關(guān)鍵字的語法結(jié)構(gòu)

除了聲明代碼塊為不安全代碼外,也可以直接構(gòu)建“不安全的”結(jié)構(gòu)、類型成員和函數(shù)。
unsafe struct Point
{
public int x;
public int y;
public Point* next;
public Point* previous;
}
unsafe static void CalcPoint(Point* point)
{
//
}
也可以在導(dǎo)入非托管 DLL 的函數(shù)聲明中使用unsafe
[DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern unsafe int memcpy(void* dest, void* src, int count);注意:
指針不能指向引用或包含引用的結(jié)構(gòu),因為無法對對象引用進行垃圾回收,即使有指針指向它也是如此。 垃圾回收器并不跟蹤是否有任何類型的指針指向?qū)ο蟆?/strong>
下面的示例代碼可以說明:
/// <summary>
/// 聲明一個Point結(jié)構(gòu)體
/// </summary>
struct Point
{
public int x;
public int y;
}
static void Main(string[] args)
{
unsafe
{
//編譯正常
Point p = new Point();
Point* pp = &p;
}
} //換成類
class Point
{
public int x;
public int y;
}
4、*和&操作符
在不安全的上下文中,可以使用 * 操作符構(gòu)建數(shù)據(jù)類型相對應(yīng)的指針類型(指針類型、值類型和引用類型,示例代碼中的type),使用 & 操作符獲取被指向的內(nèi)存地址。
type* identifier; void* identifier; //允許但不推薦
下面是使用*操作符進行指針類型聲明
int* p | p 是指向整數(shù)的指針。 |
int** p | p 是指向整數(shù)的指針的指針。 |
int*[] p | p 是指向整數(shù)的指針的一維數(shù)組。 |
char* p | p 是指向字符的指針。 |
void* p | p 是指向未知類型的指針。 |
注意:
1、無法對 void* 類型的指針應(yīng)用間接尋址運算符。 但是,你可以使用強制轉(zhuǎn)換將 void 指針轉(zhuǎn)換為任何其他指針類型,反過來也是可以的。
2、指針類型不從object類繼承,并且指針類型與 object 之間不存在轉(zhuǎn)換。 此外,裝箱和取消裝箱不支持指針。
下面的代碼演示了如何聲明指針類型:
static void Main(string[] args)
{
int []a = { 1, 2, 3, 4, 4 };
unsafe
{
//臨時固定一個變量以使它的地址可被找到
fixed (int* p = &a[0])
{
int* p2 = p;
Console.WriteLine(*p2);
p2++;
Console.WriteLine(*p2);
p2++;
Console.WriteLine(*p2);
}
}
}輸出結(jié)果如下:
1
2
3
下面的代碼演示了如何使用指針類型進行數(shù)據(jù)交換:
static void Main(string[] args)
{
int a = 1;
int b = 2;
unsafe
{
UnsafeSwap(&a, &b);
}
Console.WriteLine(a);
Console.WriteLine(b);
}
/// <summary>
/// 使用指針
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
static unsafe void UnsafeSwap(int* a,int *b)
{
int temp = *a;
*a = *b;
*b = temp;
}
/// <summary>
/// 不使用指針的安全版本
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
static void SafeSwap(ref int a,ref int b)
{
int temp = a;
a = b;
b = temp;
}輸出結(jié)果如下:
2
1
5、通過指針訪問字段
定義如下結(jié)構(gòu)體
struct Point
{
public int x;
public int y;
public override string ToString()
{
return $"x:{x},y:{y}";
}
}如果聲明一個Point類型的指針,就需要使用指針字段訪問操作符(->)來訪問公共成員(和C++一樣),也可以使用指針間接尋址操作符(*)來解除指針的引用,使其也可以使用 (.)操作符訪問字段(和C++一樣)。
static unsafe void Main(string[] args)
{
//通過指針訪問成員
Point point = new Point();
Point* p = &point;
p->x = 10;
p->y = 5;
Console.WriteLine(p->ToString());
//通過指針間接尋址訪問成員
Point point2; //不使用 new 運算符的情況下對其進行實例化,需要在首次使用實例之前必須初始化所有實例字段。
Point* p2 = &point2;
(*p2).x = 128;
(*p2).y = 256;
Console.WriteLine((*p2).ToString());
}運行結(jié)果如下:
x:10,y:5
x:128,y:256
6、stackalloc關(guān)鍵字
在不安全上下文中,可能需要聲明一個直接從調(diào)用棧分配內(nèi)存的本地變量(不受.Net垃圾回收器控制)。C#提供了與CRT函數(shù)_alloca等效的stackalloc關(guān)鍵字來滿足這個需求。
static unsafe void Main(string[] args)
{
char* p = stackalloc char[3];
for (int i = 0; i < 3; i++)
{
p[i] = (char)(i+65); //A-C
}
Console.WriteLine(*p);
Console.WriteLine(p[0]);
Console.WriteLine(*(++p));
Console.WriteLine(p[0]);
Console.WriteLine(*(++p));
Console.WriteLine(p[0]);
}輸出結(jié)果如下:
A
A
B
B
C
C
7、fixed關(guān)鍵字
在上面的示例中,我們可以看到,通過stackalloc關(guān)鍵字,在不安全上下文中分配一大塊內(nèi)存非常方便。但這塊內(nèi)存是在棧上的,當分配方法返回的時候,被分配的內(nèi)存立即被清理。
假設(shè)有如下情況:
聲明一個引用類型PointRef和一個值類型Point
class PointRef
{
public int x;
public int y;
public override string ToString()
{
return $"x:{x},y:{y}";
}
}
struct Point
{
public int x;
public int y;
public override string ToString()
{
return $"x:{x},y:{y}";
}
}調(diào)用者聲明了一個PointRef類型的變量,內(nèi)存將被分配在垃圾回收器堆上。如果一個不安全的上下文要與這個對象(或這個堆上的任何對象)交互,就可能會出現(xiàn)問題,因為垃圾回收可隨時發(fā)生。設(shè)想一下,恰好在清理堆的時候訪問Point成員,這就很
為了將不安全上下文中的引用類型變量固定,C#提供了fixed關(guān)鍵字,fixed語句設(shè)置指向托管類型的指針并在代碼執(zhí)行過程中固定該變量。換句說話:fixed關(guān)鍵字可以鎖定內(nèi)存中的引用變量。這樣在語句的執(zhí)行過程中,該變量地址保持不變。
事實上,也只有使用fixed關(guān)鍵字,C#編譯器才允許指針指向托管變量。
static unsafe void Main(string[] args)
{
PointRef pointRef = new PointRef();
Point point = new Point();
int a = &pointRef.x; //編譯不通過
int *b = &point.x; //編譯通過
fixed(int *c = &pointRef.x)
{
//編譯通過
}
}說明:
在fixed中初始化多個變量也是可以的
//同時聲明多個指針變量的語法跟C++中的不一樣,需要注意
fixed(int *e = &(pointRef.x) , f = &(pointRef.y) )
{
}8、sizeof關(guān)鍵字
在不安全上下文中,sizeof關(guān)鍵字用于獲取值類型(不是引用類型)的字節(jié)大小。sizeof可計算任何由System.ValueType派生實體的字節(jié)數(shù)。
static void Main(string[] args)
{
unsafe
{
//不安全版本
Console.WriteLine(sizeof(int));
Console.WriteLine(sizeof(float));
Console.WriteLine(sizeof(Point));
}
//安全版本
Console.WriteLine(Marshal.SizeOf(typeof(int)));
Console.WriteLine(Marshal.SizeOf(typeof(float)));
Console.WriteLine(Marshal.SizeOf(typeof(Point)));
}9、避免使用指針
事實上在C#中,指針并不是新東西。因為在代碼中可以自由使用引用 ,而引用就是一個類型安全的指針。指針只是一個存儲地址的變量,這和引用其實是一個原理。引用的主要作用是使C#更易于使用,防止用戶無意中執(zhí)行某些破壞內(nèi)存中內(nèi)容的操作。
使用指針后,可以進行低級的內(nèi)存訪問,但這是有代價的,使用指針的語法比引用類型的語法復(fù)雜得多,而且指針使用起來也比較困難,需要較高的編程技巧和強力。如果不仔細,就容易在程序中引入細微的,難以查找的錯誤。另外,如果使用指針,就必須授予代碼運行庫的代碼訪問安全機制的高級別信任,否則就不能執(zhí)行它。
MSDN上有如下關(guān)于指針的說明:
在公共語言運行時 (CLR) 中,不安全代碼是指無法驗證的代碼。 C# 中的不安全代碼不一定是危險的;只是 CLR 無法驗證該代碼的安全性。 因此,CLR 將僅執(zhí)行完全信任的程序集中的不安全代碼。 如果你使用不安全代碼,你應(yīng)該負責(zé)確保代碼不會引發(fā)安全風(fēng)險或指針錯誤。
大多數(shù)情況下,可以使用System.Intptr或ref關(guān)鍵字來替代指針完成我們想要的操作。
下面使用示例代碼說明一下:(僅供演示)
這里還是以memcpy函數(shù)為例,假設(shè)我有一個Point結(jié)構(gòu)的實例,要對這個Point進行拷貝。
聲明Point結(jié)構(gòu)
struct Point
{
public int x;
public int y;
}使用System.IntPtr:
/// <summary>
/// 使用IntPtr
/// </summary>
/// <param name="pDst"></param>
/// <param name="pSrc"></param>
/// <param name="count"></param>
/// <returns></returns>
[DllImport("msvcrt.dll", EntryPoint = "memcpy", CallingConvention = CallingConvention.Cdecl)]
private static extern unsafe int memcpyi(IntPtr pDst, IntPtr pSrc, int count); static void MemCpyIntPtr()
{
var p = new Point() { x = 200,y = 10};
Console.WriteLine(p.x + " " + p.y);
var size = Marshal.SizeOf(p);
IntPtr ptrSrc = Marshal.AllocHGlobal(size);
IntPtr ptrDest = Marshal.AllocHGlobal(size);
//將結(jié)構(gòu)體Point轉(zhuǎn)換成ptrSrc
Marshal.StructureToPtr(p, ptrSrc, false);
//memcpy
memcpyi(ptrDest, ptrSrc, size);
//再轉(zhuǎn)換成結(jié)構(gòu)體
Point p2 = new Point();
//先輸出一次進行對比
Console.WriteLine(p2.x + " " + p2.y);
p2 = (Point)Marshal.PtrToStructure(ptrDest, typeof(Point));
Console.WriteLine(p2.x + " " + p2.y);
}運行結(jié)果如下:
200 10
0 0
200 10
使用指針:
/// <summary>
/// 使用指針
/// </summary>
/// <param name="pDst"></param>
/// <param name="pSrc"></param>
/// <param name="count"></param>
/// <returns></returns>
[DllImport("msvcrt.dll", EntryPoint = "memcpy", CallingConvention = CallingConvention.Cdecl)]
private static extern unsafe int memcpyp(void* pDst, void* pSrc, int count); static unsafe void MemCpyPointer()
{
Point p = new Point() { x = 200, y = 10 };
Point p2 = new Point();
Console.WriteLine(p.x + " " + p.y);
Console.WriteLine(p2.x + " " + p2.y);
Point* pSrc = &p;
Point* pDest = &p2;
memcpyp((void*)pDest, (void*)pSrc, sizeof(Point));
p2 = *pDest;
Console.WriteLine(p2.x + " " + p2.y);
}運行結(jié)果如下:
200 10
0 0
200 10
下面介紹使用指針傳遞時的另外一種情況,這種情況我們可以使用ref來代替指針完成操作。
先用C++封裝一個庫,導(dǎo)出如下函數(shù),用來打印一個整形數(shù)組
extern "C" __declspec(dllexport) void PrintArray(int* pa,int size);
extern "C" __declspec(dllexport) void PrintArray(int* pa,int size)
{
for (size_t i = 0; i < size; i++)
{
std::cout << *pa << std::endl;
pa++;
}
}使用ref:
[DllImport("demo_lib.dll",EntryPoint = "PrintArray")]
private static extern void PrintArrayRef(ref int pa,int size); static void PrintArrayRef()
{
int[] array = new int[] { 1,2,3};
//使用ref關(guān)鍵字傳的是引用,ref[0]其實就是傳的首地址
PrintArrayRef(ref array[0], array.Length);
}運行結(jié)果:
1
2
3
使用指針:
[DllImport("demo_lib.dll", EntryPoint = "PrintArray")]
private static extern unsafe void PrintArrayPointer(int* pa, int size); static unsafe void PrintArrayPointer()
{
int size = 3;
int* array = stackalloc int[3];
for (int i = 0; i < size; i++)
{
array[i] = i+1;
}
PrintArrayPointer(array, size);
}運行結(jié)果:
1
2
3
以上就是在C#中使用指針的示例代碼的詳細內(nèi)容,更多關(guān)于C#使用指針的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C# WebService發(fā)布以及IIS發(fā)布
這篇文章主要介紹了C# WebService發(fā)布以及IIS發(fā)布的相關(guān)資料,感興趣的小伙伴們可以參考一下2016-07-07
C#動態(tài)代碼生成控件后其他事件不能獲取該控件值的解決方法
這篇文章主要給大家介紹了關(guān)于C#動態(tài)代碼生成控件后其他事件不能獲取該控件值的解決方法,文中通過圖文介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-07-07
C#運行程序時阻止關(guān)閉顯示器和系統(tǒng)待機
這篇文章介紹了C#運行程序時阻止關(guān)閉顯示器和系統(tǒng)待機的方法,文中通過示例代碼介紹的非常詳細。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-06-06

