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

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

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

