服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - C# - 如何在C#中使用指针

如何在C#中使用指针

2022-09-08 15:48一线码农上海 C#

这篇文章主要介绍了如何在C#中使用指针,文中代码简单易懂,帮助大家更好的工作和学习,感兴趣的朋友快来了解下

一:背景

1. 讲故事

高级语言玩多了,可能很多人对指针或者汇编都淡忘了,本篇就和大家聊一聊指针,虽然c#中是不提倡使用的,但你能说指针在c#中不重要吗?你要知道fcl内库中大量的使用指针,如string,encoding,filestream等等数不胜数,如例代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
   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。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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的栈上地址, 一图胜千言。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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. ++、–运算符

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

?
1
2
fixed (int* ptr = new int[3] { 1, 2, 3 }) { }
fixed (char* ptr2 = "abcd") { }

首先ptr默认指向数组在堆上分配的首地址,也就是1的内存地址,当ptr++后会进入到下一个整形元素2的内存地址,再++后又进入下一个int的内存地址,也就是3,很简单吧,我举一个例子:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  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是不可变的,你就是对它吐口痰它都会生成一个新字符串,NB的是用指针就不一样了,你可以先找到替换字符的内存地址,然后将新字符直接赋到这个内存地址上,对不对,我来写一段代码,把abcgef 替换成 abcdef, 也就是将 g 替换为 d

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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模式

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
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#中使用指针

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

四:总结

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

以上就是如何在c#中使用指针的详细内容,更多关于c# 指针的资料请关注服务器之家其它相关文章!

原文链接:https://www.imooc.com/article/304713

延伸 · 阅读

精彩推荐
  • C#WPF实现控件拖动的示例代码

    WPF实现控件拖动的示例代码

    这篇文章主要介绍了WPF实现控件拖动的示例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧...

    ludewig3962022-02-28
  • C#C#微信开发(服务器配置)

    C#微信开发(服务器配置)

    这篇文章主要介绍了C#微信开发中有关服务器配置的相关内容,感兴趣的小伙伴们可以参考一下...

    AmosHs丶11022021-11-03
  • C#C#利用性能计数器监控网络状态

    C#利用性能计数器监控网络状态

    这篇文章主要为大家详细介绍了C#利用性能计数器监控网络状态的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    飞翔的月亮9322021-12-18
  • C#C#中explicit与implicit的深入理解

    C#中explicit与implicit的深入理解

    这篇文章主要给大家介绍了关于C#中explicit与implicit的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用C#具有一定的参考学习价值,需要...

    依乐祝11782022-07-22
  • C#C#实现的自定义邮件发送类完整实例(支持多人多附件)

    C#实现的自定义邮件发送类完整实例(支持多人多附件)

    这篇文章主要介绍了C#实现的自定义邮件发送类,具有支持多人多附件的功能,涉及C#邮件操作的相关技巧,需要的朋友可以参考下...

    Autumoon5232021-11-05
  • C#C#在foreach遍历删除集合中元素的三种实现方法

    C#在foreach遍历删除集合中元素的三种实现方法

    这篇文章主要给大家总结介绍了关于C#在foreach遍历删除集合中元素的实现方法,文中通过示例代码介绍的非常详细,对大家学习或者使用C#具有一定的参考...

    willingtolove8792022-08-11
  • C#C#隐藏主窗口的方法小结

    C#隐藏主窗口的方法小结

    这篇文章主要介绍了C#隐藏主窗口的方法,列举了C#隐藏窗口的三种常用方法,涉及C#窗体操作的常用技巧,需要的朋友可以参考下...

    Microblue5632021-11-15
  • C#如何使用C#在PDF文件添加图片印章

    如何使用C#在PDF文件添加图片印章

    文档中添加印章可以起一定的作用,比如,防止文件随意被使用,或者确保文档内容的安全性和权威性。C#添加图片印章其实也有很多实现方法,这里我使...

    C#教程网11252021-12-21