8

我来自 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对象的机制(const在 C++ 中与 C# 中几乎相同readonly,而不是const在 C# 中),因为我们没有足够的理由通过指针任意修改 const 值。

进一步探索,我发现在 C# 中没有与 C++ 的低级 const 指针等效的东西,类似于:

readonly int* p

在 C# 中,如果有的话。

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

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

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

所以我的问题是:

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

注意:在评论部分:C++中的cast awayconst与这个问题无关,因为它只有在指向的对象本身不是时才有效const,否则它是UB。另外,我基本上是在谈论 C# 和编译时行为。

如果您不完全理解该问题,可以要求我提供更多详细信息。我发现大多数人都无法正确理解这一点,也许是我的错没有说清楚。

4

1 回答 1

2

我希望我可以说您只会因为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# 中的别名问题。

于 2020-04-08T15:05:33.293 回答