我想知道下面代码中的
RepeatedIDValue64_8.Get
方法在强制固定指针后返回某个内存段的引用是否安全?我们的程序出现了崩溃,我想知道是否与GC后的内存碎片整理有关?
public struct IDValue64 : IStructMessage<IDValue64>
{
public static StructMessageParser<IDValue64> Parser = new StructMessageParser<IDValue64>();
public const int SizeOf = 16;
public long Id;
public long Value;
public override string ToString()
{
MessageFormat format = new MessageFormat();
format.WriteLine("{");
format.Indent();
format.WriteLine("Id : " + Id.ToString() + ",");
format.WriteLine("Value : " + Value.ToString() + " }");
format.Outdent();
return format.ToString();
}
}
unsafe public struct RepeatedIDValue64_8
{
const int BuffSize = 128;
public const int MaxCount = 8;
public int Count;
public fixed byte Buffer[BuffSize];
public int GetCount()
{
return Count;
}
public int GetMaxCount()
{
return MaxCount;
}
public ref IDValue64 Get(int index)
{
if (index < 0 || index >= Count)
{
string message = string.Format("class RepeatedIDValue64_8 index{0} count{1}", index, Count);
throw new ArgumentOutOfRangeException(message);
}
fixed (byte* pb = Buffer)
{
IDValue64* pbs = (IDValue64*)pb;
return ref pbs[index]; // is safe?
}
}
public override string ToString()
{
StringBuilder sb = new StringBuilder();
sb.Append("{\n");
for (int i = 0; i < Count; i++)
{
sb.Append("[" + i.ToString() + "]:");
sb.Append(Get(i).ToString()); // core pos
}
sb.Append('}');
return sb.ToString();
}
}
dotnet版本是8.0
这是 coredump clrstack clrstack
首先,旁白,
public fixed byte Buffer[BuffSize];
不会使
Buffer
固定在内存中,而是将数组布局在结构体的内存中,而不是在其自己的内存块中。
但是,以固定的说法来说:
fixed (byte* pb = Buffer)
{
IDValue64* pbs = (IDValue64*)pb;
return ref span[index]; // is safe?
}
内存中的修复(来自 GC 重定位的块)仅在执行该块时适用。当包含函数返回时,块将退出,GC 可以重新定位
Buffer
(或者,如上所述,包含 Buffer
的结构体)。
来自 https://learn.microsoft.com/en-gb/dotnet/csharp/language-reference/statements/fixed
您只能在相应的
语句中使用声明的指针。声明的指针是只读的,不能修改:
fixed
从广义上讲,这是有效的,并且为您提供了一个指向缓冲区的内部指针:
fixed (byte* pb = Buffer)
{
IDValue64* pbs = (IDValue64*)pb;
return ref pbs[index]; // is safe?
}
但是,有一个问题:内部指针to在哪里?
考虑:
RepeatedIDValue64_8 SomeMethod() {...}
// ...
ref IDValue64 = ref SomeMethod().Get(0);
在此之后,我们有一个指向临时堆栈位置的指针;您想要的只是为 field 或定义的 local 位置合理定义 - 而不是堆栈帧中定义的局部变量上方(下方)的瞬态空间。这就是以下行为不合法的原因:
struct Foo // note: this problem does not apply to classes
{
private int bar;
ref int Bar => ref bar; // CS8170
}
因此,从 GC 的角度来看:是的,您可以将非托管指针强制指向固定区域内的托管指针 - 但是,仍然存在一些陷阱,您只需获取指向瞬态堆栈空间的内部指针即可最终导致损坏。