追了多年的开发框架,你还认识指针吗?
一:背景
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 = #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 = # => 0x0000005b1efff078 = 0x0000005b1efff084
int* 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' 替换成 'd'string 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];}}
有图有真相哈,直接走指针比走数组下标要快近一倍。
四:总结
希望本篇能给在框架上奔跑的您一个友情提醒,不要把指针忘啦,别人提倡不使用的指针在底层框架可都是大量使用的哦~
追了多年的开发框架,你还认识指针吗?相关推荐
- tostring会空指针吗_追了多年的开发框架,你还认识指针吗?
一:背景 1. 讲故事 高级语言玩多了,可能很多人对指针或者汇编都淡忘了,本篇就和大家聊一聊指针,虽然C#中是不提倡使用的,但你能说指针在C#中不重要吗?你要知道FCL内库中大量的使用指针,如Stri ...
- 计算机大学生必玩游戏,70%大学生用电脑玩游戏追剧,大一新生你还买电脑吗?家长们三思...
原标题:70%大学生用电脑玩游戏追剧,大一新生你还买电脑吗?家长们三思 八月到来,一张张录取通知飞向考生家庭,兴奋过后,数着开学日期,准大学生们开始准备大学用品.仔细想想,需要的东西还真不少:手机是必 ...
- 中台之上(二):为什么业务架构存在20多年,技术人员还觉得它有点虚?
业务架构这个词大家时常听到,但是能解释得清楚的却不多,撩撩度娘,你就会发现,不少人问及业务架构和应用架构的关系,聊天时,也常有人问起业务架构师和产品经理什么区别?业务架构分析和需求分析什么区别?为了思 ...
- 光头强的圆球机器人视频_《熊出没狂野大陆》快上映了,看了多年光头强,还能有新鲜动画吗...
<熊出没.狂野大陆>要上映了,大家还期待吗?故事大约是狗熊岭附近开了家"狂野大陆" ,光头强慕名来到此地,和熊二以及"神秘人"组队参加了" ...
- 中台之上(二):为什么业务架构存在 20 多年,技术人员还觉得它有点虚?
点击上方"程序猿技术大咖",关注并选择"设为星标" 回复"加群"获取入群讨论资格! 业务架构这个词大家时常听到,但是能解释得清楚的却不多,撩 ...
- 从事金融多年的老手为什么还会爆仓?
因为人性是贪婪的,利用杠杆以小博大尝到甜头的滋味,比毒品还要上瘾. 比尔黄这样的投资鬼才七年之内把自己管理的基金从两千多万美元做到了50亿美元! 在华尔街风生水起,家喻户晓.并且他得到了众多华尔街大佬 ...
- 训练时loss:0.000e+00_其他品牌还在找不到00后营销方向盘时,小度智能音箱发车了…....
任何行业的发展都与所处的社会文化.经济.价值观.群体特性有着密不可分的关联.近年来我们常说的"断舍离"."消费升级"."圈层文化"等诸多新兴 ...
- 除了霸王洗发水,还能怎么拯救程序员的发际线?
程序员的工作除了写代码就是优化代码,加班加出来的锃亮的秃顶,才是工作颁发的最佳码农勋章. 除了章光101和霸王生发剂,有没有更好的办法从根本上解决这个问题呢?答案是,有. 为了使开发更加轻松和高效,现 ...
- 如何在免费追剧?Python制作视频解析免费追剧神器
前言 同学们在闲暇之余是否喜欢看电影或者电视剧呢? 今天带领大家使用python制作能免费追剧的桌面软件.还在等什么?发车了! 效果我就不再这里演示了
最新文章
- 不能忽略的Nginx做web服务器的favicon.ico图像找不到问题
- leetcode算法题解(Java版)-9-N皇后问题
- Java高阶部分知识点汇总(二)-封装与隐藏详讲
- 面对对象课程设计报告java,面向对象编程 JAVA编程综合实验报告.doc
- 开发辅助 | 阿里图标库iconfont入门使用
- 贝塞尔修正_贝塞尔修正背后的推理:n-1
- 【WCF】无法加载协定为“ServiceReference1.xxxxxx”的终结点配置部分,因为找到了该协定的多个终结点配置。请按名称指示首选的终结点配置部分.
- 一张图之——JSDuck
- matlab数字信号处理与应用 张德丰,MATLAB数字信号处理与应用
- VS2010-MFC(常用控件:标签控件Tab Control 上)
- 专题:固体力学中应力与应变分析详解(5.主方向与主应力)
- 岁月划过生命线(从阿里到微店)
- .Net 调用中国气象台Web Service
- OpenCV(C++)图像处理基础03:读写像素、像素值修改、像素反差
- 目标检测之RFB Net
- Google Chromecast 联网激活攻略
- 如何隐藏PickerView的两条线
- suse下oracle静默安装,SUSE Linux Enterprise 11SP1静默安装Oracle 11gR2说明文档.doc
- 【NOIP2011提高组】观光公交
- IOS之 上传App预览和截屏规范