如何在C#中使用指針
一:背景
1. 講故事
高級(jí)語言玩多了,可能很多人對(duì)指針或者匯編都淡忘了,本篇就和大家聊一聊指針,雖然C#中是不提倡使用的,但你能說指針在C#中不重要嗎?你要知道FCL內(nèi)庫中大量的使用指針,如String,Encoding,FileStream
等等數(shù)不勝數(shù),如例代碼:
private unsafe static bool EqualsHelper(string strA, string strB) { fixed (char* ptr = &strA.m_firstChar) { fixed (char* ptr3 = &strB.m_firstChar) { char* ptr2 = ptr; char* ptr4 = ptr3; while (num >= 12) {...} while (num > 0 && *(int*)ptr2 == *(int*)ptr4) {...} } } } public unsafe Mutex(bool initiallyOwned, string name, out bool createdNew, MutexSecurity mutexSecurity) { byte* ptr = stackalloc byte[(int)checked(unchecked((ulong)(uint)securityDescriptorBinaryForm.Length))] } private unsafe int ReadFileNative(SafeFileHandle handle, byte[] bytes, out int hr) { fixed (byte* ptr = bytes) { num = ((!_isAsync) ? Win32Native.ReadFile(handle, ptr + offset, count, out numBytesRead, IntPtr.Zero) : Win32Native.ReadFile(handle, ptr + offset, count, IntPtr.Zero, overlapped)); } }
對(duì),你覺得的美好世界,其實(shí)都是別人幫你負(fù)重前行,退一步說,指針的理解和不理解,對(duì)你研究底層源碼影響是不能忽視的,指針相對(duì)比較抽象,考的是你的空間想象能力,可能現(xiàn)存的不少程序員還是不太明白,因?yàn)槟闳狈λ娂此玫墓ぞ撸M@一篇能幫你少走些彎路。
二:windbg助你理解
指針雖然比較抽象,但如果用windbg實(shí)時(shí)查看內(nèi)存布局,就很容易幫你理解指針的套路,下面先理解下指針的一些簡單概念。
1. &、* 運(yùn)算符
&
取址運(yùn)算符,用于獲取某一個(gè)變量的內(nèi)存地址, *
運(yùn)算符,用于獲取指針變量中存儲(chǔ)地址指向的值,很抽象吧,看windbg。
unsafe { int num1 = 10; int* ptr = &num1; int** ptr2 = &ptr; var num2 = **ptr2; } 0:000> !clrstack -l ConsoleApp4.Program.Main(System.String[]) [C:\dream\Csharp\ConsoleApp1\ConsoleApp4\Program.cs @ 26] LOCALS: 0x000000305f5fef24 = 0x000000000000000a 0x000000305f5fef18 = 0x000000305f5fef24 0x000000305f5fef10 = 0x000000305f5fef18 0x000000305f5fef0c = 0x000000000000000a
2. **運(yùn)算符
**
也叫二級(jí)指針,指向一級(jí)指針變量地址的指針,有點(diǎn)意思,如下程序:ptr2
指向的就是 ptr
的棧上地址, 一圖勝千言。
unsafe { int num1 = 10; int* ptr = &num1; int** ptr2 = &ptr; var num2 = **ptr2; } 0:000> !clrstack -l ConsoleApp4.Program.Main(System.String[]) [C:\dream\Csharp\ConsoleApp1\ConsoleApp4\Program.cs @ 26] LOCALS: 0x000000305f5fef24 = 0x000000000000000a 0x000000305f5fef18 = 0x000000305f5fef24 0x000000305f5fef10 = 0x000000305f5fef18 0x000000305f5fef0c = 0x000000000000000a
3. ++、–運(yùn)算符
這種算術(shù)操作常常用在數(shù)組或者字符串等值類型集合,比如下面代碼:
fixed (int* ptr = new int[3] { 1, 2, 3 }) { } fixed (char* ptr2 = "abcd") { }
首先ptr默認(rèn)指向數(shù)組在堆上分配的首地址,也就是1的內(nèi)存地址,當(dāng)ptr++后會(huì)進(jìn)入到下一個(gè)整形元素2的內(nèi)存地址,再++后又進(jìn)入下一個(gè)int的內(nèi)存地址,也就是3,很簡單吧,我舉一個(gè)例子:
unsafe { fixed (int* ptr = new int[3] { 1, 2, 3 }) { int* cptr = ptr; Console.WriteLine(((long)cptr++).ToString("x16")); Console.WriteLine(((long)cptr++).ToString("x16")); Console.WriteLine(((long)cptr++).ToString("x16")); } } 0:000> !clrstack -l LOCALS: 0x00000070c15fea50 = 0x000001bcaac82da0 0x00000070c15fea48 = 0x0000000000000000 0x00000070c15fea40 = 0x000001bcaac82dac 0x00000070c15fea38 = 0x000001bcaac82da8
一圖勝千言哈,Console中的三個(gè)內(nèi)存地址分別存的值是1,2,3
哈, 不過這里要注意的是,C#是托管語言,引用類型是分配在托管堆中,所以堆上地址會(huì)存在變動(dòng)的可能性,這是因?yàn)镚C會(huì)定期回收內(nèi)存,所以vs編譯器需要你用fixed把堆上內(nèi)存地址固定住來逃過GC的打壓,在本例中就是 0x000001bcaac82da0 - (0x000001bcaac82da8 +4)
三:用兩個(gè)案例幫你理解
古語說的好,一言不中,千言無用,你得拿一些例子活講活用,好吧,準(zhǔn)備兩個(gè)例子。
1. 使用指針對(duì)string中的字符進(jìn)行替換
我們都知道string中有一個(gè)replace方法,用于將指定的字符替換成你想要的字符,可是C#中的string是不可變的,你就是對(duì)它吐口痰它都會(huì)生成一個(gè)新字符串,🐮👃的是用指針就不一樣了,你可以先找到替換字符的內(nèi)存地址,然后將新字符直接賦到這個(gè)內(nèi)存地址上,對(duì)不對(duì),我來寫一段代碼,把abcgef
替換成 abcdef
, 也就是將 g
替換為 d
。
unsafe { //把 'g' 替換成 'd' string s = "abcgef"; char oldchar = 'g'; char newchar = 'd'; Console.WriteLine($"替換前:{s}"); var len = s.Length; fixed (char* ptr = s) { //當(dāng)前指針地址 char* cptr = ptr; for (int i = 0; i < len; i++) { if (*cptr == oldchar) { *cptr = newchar; break; } cptr++; } } Console.WriteLine($"替換后:{s}"); }
看輸出結(jié)果沒毛病,接下來用windbg去線程棧上找找當(dāng)前有幾個(gè)string對(duì)象的引用地址,可以在break處抓一個(gè)dump文件。
從圖中 LOCALS
中的10個(gè)變量地址來看,后面9個(gè)有帶地址的都是靠近string首地址: 0x000001ef1ded2d48
,說明并沒有新的string產(chǎn)生。
2. 指針和索引遍歷速度大比拼
平時(shí)我們都是通過索引對(duì)數(shù)組進(jìn)行遍歷,如果和指針進(jìn)行碰撞測試,您覺得誰快呢?如果我說索引方式就是指針的封裝,你應(yīng)該知道答案了吧,下面來一起觀看到底快多少???
為了讓測試結(jié)果更加具有觀賞性,我準(zhǔn)備遍歷1億個(gè)數(shù)字, 環(huán)境為:netframework4.8, release模式
static void Main(string[] args) { var nums = Enumerable.Range(0, 100000000).ToArray(); for (int i = 0; i < 10; i++) { var watch = Stopwatch.StartNew(); Run1(nums); watch.Stop(); Console.WriteLine(watch.ElapsedMilliseconds); } Console.WriteLine(" -------------- "); for (int i = 0; i < 10; i++) { var watch = Stopwatch.StartNew(); Run2(nums); watch.Stop(); Console.WriteLine(watch.ElapsedMilliseconds); } Console.WriteLine("執(zhí)行結(jié)束啦!"); Console.ReadLine(); } //遍歷數(shù)組 public static void Run1(int[] nums) { unsafe { //數(shù)組最后一個(gè)元素的地址 fixed (int* ptr1 = &nums[nums.Length - 1]) { //數(shù)組第一個(gè)元素的地址 fixed (int* ptr2 = nums) { int* sptr = ptr2; int* eptr = ptr1; while (sptr <= eptr) { int num = *sptr; sptr++; } } } } } public static void Run2(int[] nums) { for (int i = 0; i < nums.Length; i++) { int num = nums[i]; } }
有圖有真相哈,直接走指針比走數(shù)組下標(biāo)要快近一倍。
四:總結(jié)
希望本篇能給在框架上奔跑的您一個(gè)友情提醒,不要把指針忘啦,別人提倡不使用的指針在底層框架可都是大量使用的哦~
以上就是如何在C#中使用指針的詳細(xì)內(nèi)容,更多關(guān)于C# 指針的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C# 實(shí)現(xiàn)QQ式截圖功能實(shí)例代碼
本篇文章主要介紹了C# 實(shí)現(xiàn)QQ式截圖功能實(shí)例代碼,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-02-02C#利用WinForm實(shí)現(xiàn)查看指定目錄下所有圖片功能
Windows 窗體是用于生成 Windows 桌面應(yīng)用的 UI 框架, 它提供了一種基于 Visual Studio 中提供的可視化設(shè)計(jì)器創(chuàng)建桌面應(yīng)用的高效方法,本文介紹了C#利用WinForm實(shí)現(xiàn)可以查看指定目錄文件下所有圖片功能,需要的朋友可以參考下2024-05-05c# 連接access數(shù)據(jù)庫config配置
c# 連接access數(shù)據(jù)庫config配置,需要的朋友可以參考一下2013-02-02C#實(shí)現(xiàn)利用泛型將DataSet轉(zhuǎn)為Model的方法
這篇文章主要介紹了C#實(shí)現(xiàn)利用泛型將DataSet轉(zhuǎn)為Model的方法,實(shí)例分析了C#泛型的相關(guān)使用技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-07-07在C#中g(shù)lobal關(guān)鍵字的作用及其用法
global 是 C# 2.0 中新增的關(guān)鍵字,理論上說,如果代碼寫得好的話,根本不需要用到它,但是不排除一些特別的情況,比如修改別人的代碼,本文僅舉例說明。2016-03-03舊項(xiàng)目升級(jí)新版Unity2021導(dǎo)致Visual?Studio無法使用的問題
在項(xiàng)目開發(fā)過程中,不可避免的會(huì)升級(jí)開發(fā)工具。這次我在舊項(xiàng)目版本升級(jí)到新版Unity2021.2.x時(shí),出現(xiàn)Visual?Studio無法定位等問題,這里我給大家分享下解決方法,舊項(xiàng)目升級(jí)新版Unity2021導(dǎo)致Visual?Studio無法使用的問題,需要的朋友可以參考下2021-12-12通過LinQ查詢字符出現(xiàn)次數(shù)的實(shí)例方法
這篇文章主要介紹了通過LinQ查詢字符出現(xiàn)次數(shù)的實(shí)例方法,大家參考使用吧2013-11-11C# IEnumerable和IEnumerator接口淺析
本文主要介紹了C#中IEnumerable和IEnumerator接口的相關(guān)知識(shí),具有很好的參考價(jià)值,下面跟著小編一起來看下吧2017-02-02