C#中的ref关键字浅析

.NET
206
0
0
2024-02-05
标签   C#

1.前言 Ref这个关键字其实就是非托管里面的指针。它可能是一级指针也可能是二级指针。它可以直接通过托管操控内存。本篇来看下。

2.概述 一:例子 先上简单例子代码:

static string ABC(string str)
{
   return str;
}
static  string DEF(ref string str)
{
   return  str;
}
static void Main(string[] args)
{
   string str = "abcdefg";
   string str1 = ABC(str);
   string str2 = DEF(ref str);
   Console.ReadLine();
}

ABC函数的参数没有ref关键字,而DEF函数则是有ref关键字。那么它们返回的值是否相同呢?它们返回完全是相同的,虽然经过了ref修饰,但是返回的是没有带ref关键字的str,并且接受字符串的实例str2也没有带ref关键字。

二:变体 下面改一下,把它们关键字给带上,如下代码:

 static string ABC(string str)
 {
     return str;
 }
 static ref  string DEF(ref string str)
 {
     return ref  str;
 }
 static void Main(string[] args)
 {
     string str = "abcdefg";
     string str1 = ABC(str);
     string str2 = DEF(ref str);
     Console.ReadLine();
 }

str1,str2它结果还是相同的,这点可以自行试验下。为什么会出现这种情况,这个ref关键字岂不是不起作用?直接看它汇编:

            string str2 = DEF(ref str);
00007FFE4FE10792 48 8D 4D 70          lea         rcx,[rbp+70h]  
00007FFE4FE10796 E8 AD 9E 0D 00       call        Test_.Program.DEF(System.String ByRef) (07FFE4FEEA648h)  
00007FFE4FE1079B 48 89 45 38          mov         qword ptr [rbp+38h],rax  
00007FFE4FE1079F 48 8B 45 38          mov         rax,qword ptr [rbp+38h]  
00007FFE4FE107A3 48 8B 00             mov         rax,qword ptr [rax]  
00007FFE4FE107A6 48 89 45 60          mov         qword ptr [rbp+60h],rax

汇编代码很清晰的展示了,DEF函数的返回值是rax,然后又从[rax]这个内存里面读取了字符串。这说明,第一个rax实际上是指向字符串实例str2指针的指针。而第二个[rax],从rax地址里面读取值。那么[rax]则表示指向str2字符串的指针。

这点分别看下它们的内存

rax地址:0x000000B8F9F7E3C0所在地址内存:

0x000000B8F9F7E3C0  0000025ce4409b38 0000025ce4409b24 0000025ce4409b18 0000025ce4409b18 000000b8f9f7e4a8

我们继续看下[rax]内存,

也就是上面的0000025ce4409b38表示的内存

0x0000025CE4409B38  00007ffe4fe0fd10 0062006100000007 0066006500640063 0000000000000067 0000000000000000  .??O?.......a.b.c.d.e.f.g...............

看到rax指向[rax],而[rax]则指向字符串实例str2的MethodTable。后面跟的就是字符串:abcdefg。

实际上这里的没加ref跟加了ref返回的同一结果,原因就在于JIT编译做了取地址值处理。导致的。 那么问题来了,我要直接得到地址操作字符串应该怎么做呢? 2.拆解: 既然字符串直接操作不了(这里不用span的话),那么这里把它拆解成字符进行内存操作赋值

static void Main(string[] args)
{
  string str = "abcdefg";
  ref char str1 = ref MemoryMarshal.GetReference<char>(str);
  Console.ReadLine();
}

这返回的str1就是指向str字符串实例的第一个字符a.

如果想要改下这个字符串实例str则可以如下:

static void Main(string[] args)
{
  string str = "abcdefg";
  ref char str1 = ref MemoryMarshal.GetReference<char>(str+3);
  str1='b';
  Console.ReadLine();
}

把字符串的str1的第一个字符改成b。str1的值变成了:bbcdefg了。为什么这里能改,因为str1指向的是字符串的首地址,这里直接把b写入了首地址指向的空间。