我一直在使用System.Runtime.CompilerServices.Unsafe MSIL软件包进行一些极其不安全且有点无用的混乱,该软件包使您可以使用C#中无法使用的指针来做很多事情。我创建了一个扩展方法,该方法返回一个ref byte,该字节在对象的开始处是“方法表”指针的开始,它使您可以使用固定语句中的任何对象,并以一个字节指针作为对象的开始。物体:

public static unsafe ref byte GetPinnableReference(this object obj)
{
    return ref *(byte*)*(void**)Unsafe.AsPointer(ref obj);
}


然后,我决定使用以下代码对其进行测试:

[StructLayout(LayoutKind.Explicit, Pack = 0)]
public class Foo
{
    [FieldOffset(0)]
    public string Name = "THIS IS A STRING";
}

[StructLayout(LayoutKind.Explicit, Pack = 0)]
public struct Bar
{
    [FieldOffset(0)]
    public string Name;
}


然后在方法

        var foo = new Foo();
        //var foo = new Bar { Name = "THIS IS A STRING" };

        fixed (byte* objPtr = foo)
        {
            char* stringPtr = (char*)(objPtr + (foo is Foo ?  : 12));

            for (var i = 0; i < foo.Name.Length; i++)
            {
                Console.Write(*(stringPtr + i /* Char offset */));
            }

            Console.WriteLine();
        }

        Console.ReadKey();


真正奇怪的是,它成功打印了“这是一个字符串”吗?该代码如下所示:


获取指向对象开头的字节指针objPtr
加16即可得出实际数据
再添加16个,以使字符串标头超过字符串的实际数据
加4以跳过字符串的前4个字节,即int _stringLength(作为我们的Length属性公开)
将结果解释为char指针


编辑:重要点-切换foo键入Bar时,我只添加12个字节而不是36个字节(36 = 16 + 16 + 4)。为什么在结构中标头只有8个字节,而在类中却只有32个?结构具有较小的标头(我相信没有syncblk)会很有意义,但是为什么字符串还没有16字节的标头呢?我希望偏移量是8 + 16 + 4(28),而不只是8 + 4(12)
但是,这种假设有很大的缺陷。假定该字符串以内联方式存储在class/struct中。但是,字符串是引用类型,据我所知,仅对它们的引用存储在对象内部。特别是,我认为引用类型只能放在堆上-并且因为此结构是局部变量,所以我认为它在堆栈上。如果不是,代码肯定会看起来更像这样以获得stringPtr

byte** stringRefptr = objPtr + 16;
char* stringPtr = (char*)(*stringRefPtr + 20);


在这里将字符串引用作为byte**,然后使用它来获取字符。而且如果内部字符串是char[]还是没有意义的(我不确定是否是)

那么,当string是引用类型时,即使它错误地假定string是内联存储的,为什么仍能工作并打印字符串呢?

注意:需要具有System.Runtime.CompilerServices.Unsafe nuGet软件包和C#7.3+的.NET Core 2.0+。

最佳答案

因为字符串确实是内联存储的。您的假设问题在于字符串不是普通对象,而是CLR作为特殊情况处理的(可能出于性能原因)。

对于对象,由于字符串是唯一的成员,因此这自然是分配内存的最有效方法。尝试在您的字符串成员之后添加更多成员,这样您的代码就会中断。

以下是有关如何在CLR中存储字符串的一些参考

https://mattwarren.org/2016/05/31/Strings-and-the-CLR-a-Special-Relationship/

https://codeblog.jonskeet.uk/2011/04/05/of-memory-and-strings/

编辑:我没有核对,但我相信您在抵销额后面的推理已关闭。 36 = 24(对象的大小)+ 8(字符串标题?)+ 4(int的大小),而对于struct,24字节变为0,因为它没有标题。

10-07 19:52
查看更多