番禺市桥做网站公司,服务哪家好网站制作,湖南省住房城乡建设厅网站,最近发生的新闻一#xff1a;背景1. 讲故事高级语言玩多了#xff0c;可能很多人对指针或者汇编都淡忘了#xff0c;本篇就和大家聊一聊指针#xff0c;虽然C#中是不提倡使用的#xff0c;但你能说指针在C#中不重要吗#xff1f;你要知道FCL内库中大量的使用指针#xff0c;如String,E… 一背景1. 讲故事高级语言玩多了可能很多人对指针或者汇编都淡忘了本篇就和大家聊一聊指针虽然C#中是不提倡使用的但你能说指针在C#中不重要吗你要知道FCL内库中大量的使用指针如String,Encoding,FileStream等等数不胜数如例代码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));}}
对你觉得的美好世界其实都是别人帮你负重前行退一步说指针的理解和不理解对你研究底层源码影响是不能忽视的指针相对比较抽象考的是你的空间想象能力可能现存的不少程序员还是不太明白因为你缺乏所见即所得的工具希望这一篇能帮你少走些弯路。二windbg助你理解指针虽然比较抽象但如果用windbg实时查看内存布局就很容易帮你理解指针的套路下面先理解下指针的一些简单概念。1. 、* 运算符取址运算符用于获取某一个变量的内存地址, *运算符用于获取指针变量中存储地址指向的值很抽象吧看windbg。 unsafe{int num 10;int* ptr num;var num2 *ptr;Console.WriteLine(num2);}0:000 !clrstack -l
OS Thread Id: 0x41ec (0)Child SP IP Call Site
0000005b1efff040 00007ffc766208e2 *** WARNING: Unable to verify checksum for ConsoleApp4.exe
ConsoleApp4.Program.Main(System.String[]) [C:\dream\Csharp\ConsoleApp1\ConsoleApp4\Program.cs 25]LOCALS:0x0000005b1efff084 0x000000000000000a0x0000005b1efff078 0x0000005b1efff0840x0000005b1efff074 0x000000000000000a
仔细观察 LOCALS 中三组键值对。1 int* ptr num; 0x0000005b1efff078 0x0000005b1efff084int* ptr叫做指针变量既然是变量必须得有自己的栈上地址 0x0000005b1efff078 而这个地址上的值为 0x0000005b1efff084这不就是num的栈地址嘛嘿嘿。2 var num2 *ptr; 0x0000005b1efff074 0x000000000000000a*ptr 就是用ptr的value [0x0000005b1efff084] 获取这个地址指向的值所以就是10啦。如果不明白我画一张图这可是重中之重哦~2. **运算符** 也叫二级指针指向一级指针变量地址的指针有点意思如下程序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 0x000000000000000a0x000000305f5fef18 0x000000305f5fef240x000000305f5fef10 0x000000305f5fef180x000000305f5fef0c 0x000000000000000a
3. 、--运算符这种算术操作常常用在数组或者字符串等值类型集合比如下面代码 fixed (int* ptr new int[3] { 1, 2, 3 }) { }fixed (char* ptr2 abcd) { }
首先ptr默认指向数组在堆上分配的首地址也就是1的内存地址当ptr后会进入到下一个整形元素2的内存地址再后又进入下一个int的内存地址也就是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 -lLOCALS:0x00000070c15fea50 0x000001bcaac82da00x00000070c15fea48 0x00000000000000000x00000070c15fea40 0x000001bcaac82dac0x00000070c15fea38 0x000001bcaac82da8
一图胜千言哈Console中的三个内存地址分别存的值是1,2,3哈 不过这里要注意的是C#是托管语言引用类型是分配在托管堆中所以堆上地址会存在变动的可能性这是因为GC会定期回收内存所以vs编译器需要你用fixed把堆上内存地址固定住来逃过GC的打压在本例中就是 0x000001bcaac82da0 - (0x000001bcaac82da8 4)。三用两个案例帮你理解古语说的好一言不中千言无用你得拿一些例子活讲活用好吧准备两个例子。1. 使用指针对string中的字符进行替换我们都知道string中有一个replace方法用于将指定的字符替换成你想要的字符可是C#中的string是不可变的你就是对它吐口痰它都会生成一个新字符串????????的是用指针就不一样了你可以先找到替换字符的内存地址然后将新字符直接赋到这个内存地址上对不对我来写一段代码把abcgef 替换成 abcdef, 也就是将 g 替换为 d。 unsafe{//把 g 替换成 dstring s abcgef;char oldchar g;char newchar d;Console.WriteLine($替换前:{s});var len s.Length;fixed (char* ptr s){//当前指针地址char* cptr ptr;for (int i 0; i len; i){if (*cptr oldchar){*cptr newchar;break;}cptr;}}Console.WriteLine($替换后:{s});}----- output ------替换前:abcgef
替换后:abcdef
执行结束啦
看输出结果没毛病接下来用windbg去线程栈上找找当前有几个string对象的引用地址可以在break处抓一个dump文件。从图中 LOCALS 中的10个变量地址来看后面9个有带地址的都是靠近string首地址 0x000001ef1ded2d48说明并没有新的string产生。2. 指针和索引遍历速度大比拼平时我们都是通过索引对数组进行遍历如果和指针进行碰撞测试您觉得谁快呢如果我说索引方式就是指针的封装你应该知道答案了吧下面来一起观看到底快多少为了让测试结果更加具有观赏性我准备遍历1亿个数字 环境为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(执行结束啦);Console.ReadLine();}//遍历数组public static void Run1(int[] nums){unsafe{//数组最后一个元素的地址fixed (int* ptr1 nums[nums.Length - 1]){//数组第一个元素的地址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];}}
有图有真相哈直接走指针比走数组下标要快近一倍。四总结希望本篇能给在框架上奔跑的您一个友情提醒不要把指针忘啦别人提倡不使用的指针在底层框架可都是大量使用的哦~