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写入了首地址指向的空间。