溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

C#中使用指針的方法

發(fā)布時間:2020-07-18 17:40:45 來源:億速云 閱讀:282 作者:小豬 欄目:編程語言

這篇文章主要講解了C#中使用指針的方法,內(nèi)容清晰明了,對此有興趣的小伙伴可以學(xué)習(xí)一下,相信大家閱讀完之后會有幫助。

一:背景

1. 講故事

高級語言玩多了,可能很多人對指針或者匯編都淡忘了,本篇就和大家聊一聊指針,雖然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));
		}
 } 

對,你覺得的美好世界,其實(shí)都是別人幫你負(fù)重前行,退一步說,指針的理解和不理解,對你研究底層源碼影響是不能忽視的,指針相對比較抽象,考的是你的空間想象能力,可能現(xiàn)存的不少程序員還是不太明白,因?yàn)槟闳狈λ娂此玫墓ぞ?,希望這一篇能幫你少走些彎路。

二:windbg助你理解

指針雖然比較抽象,但如果用windbg實(shí)時查看內(nèi)存布局,就很容易幫你理解指針的套路,下面先理解下指針的一些簡單概念。

1. &、* 運(yùn)算符

&取址運(yùn)算符,用于獲取某一個變量的內(nèi)存地址, *運(yùn)算符,用于獲取指針變量中存儲地址指向的值,很抽象吧,看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)算符

** 也叫二級指針,指向一級指針變量地址的指針,有點(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

C#中使用指針的方法

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++后會進(jìn)入到下一個整形元素2的內(nèi)存地址,再++后又進(jìn)入下一個int的內(nèi)存地址,也就是3,很簡單吧,我舉一個例子:

  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

C#中使用指針的方法

一圖勝千言哈,Console中的三個內(nèi)存地址分別存的值是1,2,3哈, 不過這里要注意的是,C#是托管語言,引用類型是分配在托管堆中,所以堆上地址會存在變動的可能性,這是因?yàn)镚C會定期回收內(nèi)存,所以vs編譯器需要你用fixed把堆上內(nèi)存地址固定住來逃過GC的打壓,在本例中就是 0x000001bcaac82da0 - (0x000001bcaac82da8 +4)

三:用兩個案例幫你理解

古語說的好,一言不中,千言無用,你得拿一些例子活講活用,好吧,準(zhǔn)備兩個例子。

1. 使用指針對string中的字符進(jìn)行替換

我們都知道string中有一個replace方法,用于將指定的字符替換成你想要的字符,可是C#中的string是不可變的,你就是對它吐口痰它都會生成一個新字符串,🐮👃的是用指針就不一樣了,你可以先找到替換字符的內(nèi)存地址,然后將新字符直接賦到這個內(nèi)存地址上,對不對,我來寫一段代碼,把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)前有幾個string對象的引用地址,可以在break處抓一個dump文件。

C#中使用指針的方法

從圖中 LOCALS 中的10個變量地址來看,后面9個有帶地址的都是靠近string首地址: 0x000001ef1ded2d48,說明并沒有新的string產(chǎn)生。

2. 指針和索引遍歷速度大比拼

平時我們都是通過索引對數(shù)組進(jìn)行遍歷,如果和指針進(jìn)行碰撞測試,您覺得誰快呢?如果我說索引方式就是指針的封裝,你應(yīng)該知道答案了吧,下面來一起觀看到底快多少???

為了讓測試結(jié)果更加具有觀賞性,我準(zhǔn)備遍歷1億個數(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ù)組最后一個元素的地址
    fixed (int* ptr1 = &nums[nums.Length - 1])
    {
     //數(shù)組第一個元素的地址
     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];
   }
  }

C#中使用指針的方法

有圖有真相哈,直接走指針比走數(shù)組下標(biāo)要快近一倍。

看完上述內(nèi)容,是不是對C#中使用指針的方法有進(jìn)一步的了解,如果還想學(xué)習(xí)更多內(nèi)容,歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI