【发布时间】:2018-01-15 21:44:20
【问题描述】:
警告:这只是对那些热衷于破坏东西以了解其机制的人的练习。
我正在探索我可以在 C# 中完成的限制,我编写了一个 ForceCast() 函数来执行强力强制转换,而无需任何类型检查。 永远不要考虑在生产代码中使用这个函数。
我编写了一个名为Original 的类和一个名为LikeOriginal 的结构,它们都带有两个整数变量。在Main() 中,我创建了一个名为orig 的新变量,并将其设置为Original 的新实例,其中a=7 和b=20。当orig 转换为LikeOriginal 并存储在casted 中时,cG 和dG 的值将变为未定义,这是可以预料的,因为LikeOriginal 是一个结构,而类实例包含的元数据比结构多实例从而导致内存布局不匹配。
示例输出:
Casted Original to LikeOriginal
1300246376, 542
1300246376, 542
added 3
Casted LikeOriginal back to Original
1300246379, 545
但是请注意,当我调用 casted.Add(3) 并转换回 Original 并打印 a 和 b 的值时,令人惊讶的是它们成功地增加了 3,并且这是可重复的。
让我感到困惑的是,将类转换为结构会导致cG 和dG 映射到类元数据,但是当它们被修改并转换回类时,它们会正确映射到@987654342 @和b。
为什么会这样?
使用的代码:
using System;
using System.Runtime.InteropServices;
namespace BreakingStuff {
public class Original {
public int a, b;
public Original(int a, int b)
{
this.a = a;
this.b = b;
}
public void Add(int val)
{
}
}
public struct LikeOriginal {
public int cG, dG;
public override string ToString() {
return cG + ", " + dG;
}
public void Add(int val) {
cG += val;
dG += val;
}
}
public static class Program {
public unsafe static void Main() {
Original orig = new Original(7, 20);
LikeOriginal casted = ForceCast<Original, LikeOriginal>(orig);
Console.WriteLine("Casted Original to LikeOriginal");
Console.WriteLine(casted.cG + ", " + casted.dG);
Console.WriteLine(casted.ToString());
casted.Add(3);
Console.WriteLine("added 3");
orig = ForceCast<LikeOriginal, Original>(casted);
Console.WriteLine("Casted LikeOriginal back to Original");
Console.WriteLine(orig.a + ", " + orig.b);
Console.ReadLine();
}
//performs a pointer cast but with the same memory layout.
private static unsafe TOut ForceCast<TIn, TOut>(this TIn input) {
GCHandle handle = GCHandle.Alloc(input);
TOut result = Read<TOut>(GCHandle.ToIntPtr(handle));
handle.Free();
return result;
}
private static unsafe T Read<T>(this IntPtr address) {
T obj = default(T);
if (address == IntPtr.Zero)
return obj;
TypedReference tr = __makeref(obj);
*(IntPtr*) (&tr) = address;
return __refvalue(tr, T);
}
}
}
【问题讨论】:
-
所以您真正要问的问题是:“
struct和class是否共享相同的元数据内存布局?” -
您应该为成员分配一些“明显”的值,例如
0xDEADBEEF。然后从this指针打印一个十六进制转储,以确定数据成员的实际偏移量。我怀疑它位于偏移量 0 处,这使您能够做您正在做的事情。正如 Zorn 所说,元数据(类型信息、vtable)可能在this指针之前。 -
请记住,结构是按值复制的...这与
LikeOriginal casted = ForceCast<Original, LikeOriginal>(orig);代码行有关...您将获得 ForceCast 结果的副本! -
*(IntPtr*) (&tr) = address是一种可靠的强制转换引用方式的想法在涉及值类型时显然会失效。如果你将Original设为一个结构,你仍然会得到垃圾,即使类型现在在布局上是相同的。如果您随后将ForceCast从Original变为Original,您将仍然返回垃圾,即使类型现在相同。TypedReference不是一个简单的指针,你可以用 C++ 重新解释它;运行时以完全不同的方式处理引用和值类型。 -
如果内存布局匹配,我不会得到垃圾,你试过了吗?
标签: c# class struct casting memory-layout