指针可以用来修改只读字段吗?但为什么呢



我来自C++,在C++和C#中发现指针之间的行为非常不同。

我惊讶地发现这段代码编译。。。甚至。。。工作非常完美。

class C
{
private readonly int x = 0;
unsafe public void Edit()
{
fixed (int* p = &x)
{
*p = x + 1;
}
Console.WriteLine($"Value: {x}");
}
}

这让我非常困惑,即使在C++中,我们也有一种保护const对象的机制(C++中的const与C#中的readonly几乎相同,而不是C#中的const(,因为我们没有足够的理由通过指针任意修改常量值。

进一步探索,我发现在C#中没有等价于C++的低级别常量指针,它将类似于:

readonly int* p

在C#中,如果它有一个。

那么p只能读取指向的对象,而不能向其写入

对于const对象,C#禁止尝试检索它们的地址

在C++中,任何修改const的尝试都是编译错误或未定义行为。在C#中,我不知道我们是否有可能利用这一点。

所以我的问题是:

  1. 这种行为真的定义明确吗?虽然我知道在C#中没有像UB这样的概念++
  2. 如果是,我应该如何使用它?或者从不使用它

注意:在注释部分:在C++中去掉const与这个问题无关,因为只有当指向的对象本身不是const时它才有效,否则它就是UB。另外,我主要讨论的是C#和编译时行为。

如果你不完全理解这个问题,你可以要求我提供更多的细节。我发现大多数人都不能正确理解这一点,也许是我没有把它说清楚。

我希望我可以说,您只会因为unsafe关键字而得到这种意外行为。不幸的是,至少还有两种方法可以更改只读字段,甚至不需要不安全。你可以在Joe Duffy的博客文章"When is a readonly field not readonly"中找到更多关于这些方法的信息。

通过将字段与非只读结构重叠:

class C
{
private readonly int x = 0;
class other_C
{
public int x;
}
[StructLayout(LayoutKind.Explicit)]
class Overlap_them
{
[FieldOffset(0)] public C actual;
[FieldOffset(0)] public other_C sneaky;
}
public void Edit()
{
Overlap_them overlapper = new Overlap_them();
overlapper.actual = this;
overlapper.sneaky.x = 1;
Console.WriteLine($"Value: {x}");
}
}

通过意外地用ref参数混淆this(这是从外部完成的(:

class C
{
private readonly int x = 0;
public C(int X)
{
x = X;
}
public void Edit(ref C foo)
{
foo = new C(1);
Console.WriteLine($"Value: {x}");
}
}
private static void Main()
{
var c = new C(0);
c.Edit(ref c);
}

这三种方法都是定义完善的C#,你应该像瘟疫一样避免。想象一下,调试代码时会遇到来自类外的复杂别名问题。不幸的是,仍然没有任何令人满意的解决方案来阻止C#中的混叠问题。

最新更新