追了多年的开发框架,你还认识指针吗?

一:背景

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 = &num; => 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];}}

有图有真相哈,直接走指针比走数组下标要快近一倍。

四:总结

希望本篇能给在框架上奔跑的您一个友情提醒,不要把指针忘啦,别人提倡不使用的指针在底层框架可都是大量使用的哦~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/309890.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

linux apt-get 安装 根目录,技术|apt-get 和 apt-cache 命令实例展示

apt-get和apt-cache是Ubuntu Linux中的命令行下的包管理工具。 apt-get的GUI版本是Synaptic包管理器。本篇中我们会展示apt-get和apt-cache命令的15个不同例子。示例&#xff1a;1 列出所有可用包linuxtechilocalhost:~$ apt-cache pkgnamesaccount-plugin-yahoojpceph-fusedvd…

[PAT乙级]1031 查验身份证

一个合法的身份证号码由17位地区、日期编号和顺序编号加1位校验码组成。校验码的计算规则如下&#xff1a; 首先对前17位数字加权求和&#xff0c;权重分配为&#xff1a;{7&#xff0c;9&#xff0c;10&#xff0c;5&#xff0c;8&#xff0c;4&#xff0c;2&#xff0c;1&am…

Long Path CodeForces - 407B(动态规划+思维+公式推导)

题意&#xff1a; 起点为1&#xff0c;终点为n1&#xff0c;对应第i个各点&#xff0c;如果我奇数次到达i点&#xff0c;那么下一步走到a【i】的位子&#xff0c;如果是偶数次到达&#xff0c;那么下一步走到i1的位子。 问从1走到n1一共需要走多少步&#xff1f;结果对1e97取模…

[PAT乙级]1032 挖掘机技术哪家强

为了用事实说明挖掘机技术到底哪家强&#xff0c;PAT 组织了一场挖掘机技能大赛。现请你根据比赛结果统计出技术最强的那个学校。 输入格式&#xff1a; 输入在第 1 行给出不超过 10​5​​ 的正整数 N&#xff0c;即参赛人数。随后 N 行&#xff0c;每行给出一位参赛者的信息…

断!舍!离!

最近颈椎病很严重&#xff0c;各种寻医问药&#xff0c;找各路中西医专家治疗。无奈&#xff0c;一直不见好。每周我都会去一个中医大夫那里做推拿&#xff0c;有一次我抱怨&#xff0c;这个病怎么这么难弄好。大夫笑了笑&#xff0c;说&#xff1a;颈椎病其实是个哲学问题。我…

linux权限746,linux文件权限学习笔一

linux文件权限学习随笔一linux中&#xff0c;文件权限一直是困扰初学者的难题。但是还必须要把文件权限搞明白&#xff0c;否则你就很难进一步学习linux&#xff0c;因为你听不懂他的说什么&#xff0c;看不懂他的身份&#xff0c;不知道他要做什么。我已经习惯使用windows&…

Compound Words UVA - 10391(c++用法中substr函数用法+map实现)

题意&#xff1a; 给出字典中一堆单词&#xff0c;单词的输入方式是以字典序输入的。问&#xff1a;在这一堆单词中&#xff0c;有那些单词是通过其它两个单词组合而来的。按字典序升序输出这些单词。 题目&#xff1a; You are to find all the two-word compound words in…

[C++11]返回值类型后置

在泛型编程中&#xff0c;可能需要通过参数的运算来得到返回值类型。 语法: auto func(参数1,参数2,...)->decltype(参数表达式)代码如下: #include <iostream> using namespace std;//template<typename R,typename T,typename U> //R add01(T t, U u) //{ …

在鹅厂面试5轮后扑街!微服务架构,我拿什么拯救你!

上周五接到朋友的电话&#xff0c;此前他一路披荆斩棘&#xff0c;离鹅厂Offer大概仅一步之遥。电话一接通我就说了一通让他请客吃饭的话&#xff0c;对面沉默了几秒钟&#xff0c;淡淡地说了句 “我终面没过....” 这让我一时语塞不知如何安慰他。两个中年油腻男硬是打了2小时…

linux加大ram 内核需要,Linux 5.1内核发布:io_uring接口+支持持久性内存用作RAM

拼 命 加 载 中 ...Linus Torvalds今天发布了Linux Kernel 5.1内核&#xff0c;这是一个功能强大的内核分支&#xff0c;它带来了许多重要的新功能&#xff0c;包括但不限于&#xff1a;改进了对IntelFastboot的支持&#xff1b;对Intel 22260 Wi-Fi的支持&#xff1b;对Raspbe…

Minimum Inversion Number HDU - 1394(求一个数字环的逆序对+多种解法)

题意&#xff1a; 给出n个数&#xff08;0~n-1&#xff0c;每个数仅出现一次&#xff09;,问它长为n的循环序列中逆序对最少的数量。 多种解法&#xff1a;暴力树状数组分治规律推导公式 题目&#xff1a; The inversion number of a given number sequence a1, a2, …, an…

C++实现拓扑排序(vector模拟邻接表存储,栈实现)

代码如下: #include<iostream> #include <vector> #include <string> #include <stack> using namespace std; const int N 10010;int in[N]; vector<int>v[N]; vector<int>printElem;int main() {int n, m;while (cin >> n >&…

如何借助Kubernetes实现持续的业务敏捷性

导语在当今以数字为动力&#xff0c;以客户为中心的世界中&#xff0c;敏捷性已成为组织提高其创建软件的速度、提高响应能力以满足不断变化的客户需求、保持敏捷性以适应变化的关键要素。敏捷不是商品&#xff0c;无法购买或轻易获得。要为组织创造可持续的敏捷性&#xff0c;…

Find them, Catch them POJ - 1703(种类并查集)

题意&#xff1a; 在这个城市里有两个黑帮团伙&#xff0c;现在给出N个人&#xff0c;问任意两个人他们是否在同一个团伙 1.输入D x y代表x于y不在一个团伙里 2.输入A x y要输出x与y是否在同一团伙或者不确定他们在同一个团伙里 题目&#xff1a; The police office in Tadu…

C++实现拓扑排序(邻接表存储,栈实现)

代码如下: #include <iostream> #include <stack> using namespace std; const int N 10010; using vnodeType int; typedef struct Node {int adj;int w;Node *next; }Node;typedef struct Vnode {int indegree;vnodeType v;Node *firstEdge; }Vnode;class Gra…

报复性降薪潮来袭

点击蓝字关注&#xff0c;回复“职场进阶”获取职场进阶精品资料一份最近不少读者问我&#xff1a;“洋哥&#xff0c;公司要全员降薪了&#xff0c;我该留下还是走呢”。今年的职场环境的确很残酷&#xff0c;不少公司直接破产&#xff0c;还有很多公司开始降薪裁员来保证现金…

linux 的网络操作与配置文件,Linux常用文件与网络操作命令速记指南

ls复制代码代码如下:$ ls #查看当前目录下文件conf lnmp_install.sh README vhost_ngx_pagespeed.shinit.sh ngx_pagespeed.sh source vhost.sh复制代码代码如下:$ ls conf #查看conf目录下文件index.html nginx.conf pureftpd-mysql.conf tz.phpinit.d.nginx pure-ftpd.conf s…

[C++11]通过using定义基础类型和函数指针别名

1.定义别名 语法: typedef 旧的类型名 新的类型名; typedef unsigned int uint_t;using 新的类型 旧的类型; using uint_t int ;通过using和typedef的语法格式可以看到二者的使用没有太大的区别&#xff0c;假如我们定义一个函数指针&#xff0c;using的优势就凸显出来了&…

基于 abp vNext 和 .NET Core 开发博客项目

介绍此个人博客项目底层基于 ABP Framework (不完全依赖)搭建项目 和免费开源跨平台的 .NET Core 3.1 开发&#xff0c;可作为 .NET Core 入门项目进行学习&#xff0c;支持各种主流数据库(SqlServer、MySQL、PostgreSql、Sqlite)接入&#xff0c;接口遵循 RESTful API 接口规范…

添加库路经 linux,linux下的静态库与动态库

文件名形如 libxxx.so&#xff0c;其中so是 Shared Object 的缩写&#xff0c;即可以共享的目标文件。生成动态库常用 gcc 命令&#xff1b;举例&#xff1a;编写头文件&#xff1a;ok.h 文件#ifndef __OK_H__#define __OK_H__voidok();#endif编写 ok.c 文件#include "ok.…