软件编程
位置:首页>> 软件编程>> C#编程>> 如何在C#中使用指针

如何在C#中使用指针

作者:一线码农上海  发布时间:2022-07-02 16:09:47 

标签:c#,指针

一:背景

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 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. **运算符

** 也叫二级指针,指向一级指针变量地址的指针,有点意思,如下程序: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. ++、–运算符

这种算术操作常常用在数组或者字符串等值类型集合,比如下面代码:


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 -l
LOCALS:
 0x00000070c15fea50 = 0x000001bcaac82da0
 0x00000070c15fea48 = 0x0000000000000000
 0x00000070c15fea40 = 0x000001bcaac82dac
 0x00000070c15fea38 = 0x000001bcaac82da8

如何在C#中使用指针

一图胜千言哈,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}");
  }

看输出结果没毛病,接下来用windbg去线程栈上找找当前有几个string对象的引用地址,可以在break处抓一个dump文件。

如何在C#中使用指针

从图中 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];
  }
 }

如何在C#中使用指针

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

四:总结

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

来源:https://www.imooc.com/article/304713

0
投稿

猜你喜欢

  • 打包发布jar包部署相对较为简单,尤其是在分布式服务比较多的情况下。单体项目如果是单体项目,只需要找到maven的插件,点击package运
  • 一个简单的Java web服务器实现,比较简单,基于java.net.Socket和java.net.ServerSocket实现;一、程序
  •  大家好,今天尝试用swing技术写一个贪吃蛇大作战小游戏,供大家参考。 效果展示效果展示一、游戏界面二、得分情况&nb
  • C#提升管理员权限修改本地Windows系统时间在桌面应用程序开发过程中,需要对C盘下进行文件操作或者系统参数进行设置,例如在没有外网的情况
  • 1 前言为什么我们在使用SpringBoot框架开发Java Web应用需要引入大量的starter?例如,我们引入Redis就在Maven
  • 在项目开发上,hibernate提供的经验简化了不少工作量和兼容性,但这些绝对需要有经验后才能明白,对于新手来说使用起来很困难。hibern
  • 文档中可通过应用不同的字体来呈现不一样的视觉效果,通过字体来实现文档布局、排版等设计需要。应用字体时,可在创建文档时指定字体,也可以用新字体
  • GSYVideoPlayerGSYVideoPlayer官方地址GSYVideoPlayer 一个基于IJkPlayer的播放器支持调节声音
  • 一、添加数据1、在主表中添加从表数据在景点的住宿集合(Lodgings)中增加一个度假区(Resort)var dest = (from d
  • 本文记录刚接触Android开发搭建环境后新建工程各种可能的报错,并亲身经历漫长的解决过程(╥╯^╰╥),寻找各种偏方,避免大家采坑,希望能
  • 本文实例讲述了Java调用Shell命令的方法。分享给大家供大家参考。具体如下:近日项目中有这样一个需求:系统中的外币资金调度完成以后,要将
  • 由其他进制转换为十进制比较简单,下面着重谈一谈十进制如何化为其他进制。1.使用Java带有的方法Integer,最简单粗暴了,代码如下//使
  • 一棵二叉查找树是按二叉树结构来组织的。这样的树可以用链表结构表示,其中每一个结点都是一个对象。结点中除了数据外,还包括域left,right
  • 1、找准入口,使用ClassPathXmlApplicationContext的构造方法加载配置文件,用于加载classPath下的配置文件
  • 前言这是几个月前写的博文,睡前看了觉得有些敷衍,还是改了再发吧。之前的博客做了个锁屏应用,在以前各种酷炫的锁屏效果是很流行的,有时候会去锁屏
  • 一、使用无参构造方法创建二、使用静态工厂创建三、使用实例工厂创建来源:https://www.cnblogs.com/jock766/p/1
  • 在学习Spring的过程中,留下一下痕迹。代理模式,其实就是让别人做同样的事情,但是别人却不仅将我的事情做了,还会把他的事情也做了,换言之,
  • 概述在Spring中,我们可以通过 @Autowired注解的方式为一个方法中注入参数,那么这种方法背后到底发生了什么呢,这篇文章将讲述如何
  • 安装hbase首先下载hbase的最新稳定版本 http://www.apache.org/dyn/closer.cgi/hbas
  • 一.mybatis的配置1.1 添加相应的jar包在lib文件夹下面添加mybatis的核心jar包以及依赖的jar包同在lib文件夹下面加
手机版 软件编程 asp之家 www.aspxhome.com