指针可以用来修改只读字段吗?但为什么?
Can pointers be used to modify readonly field? But why?
我来自 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
几乎是一样的,不是const
in C#), 因为我们没有足够的理由通过指针任意修改const值。
进一步探索,我发现在 C# 中没有与 C++ 的 低级常量指针 等价的东西,类似于:
readonly int* p
在 C# 中,如果有的话。
那么p只能读指向的对象,不能写。
对于 const
个对象,C# 禁止尝试检索它们的地址。
在 C++ 中,任何修改 const
的尝试都是编译错误或未定义行为。在C#中,不知道有没有可能利用这个。
所以我的问题是:
- 这种行为真的很明确吗?虽然我知道在 C# 中没有像 C++ 中的 UB 这样的概念
- 如果可以,我该如何使用?或者从不使用它?
注:评论区:cast away const
in C++ 与本题无关,因为只有指向的对象本身不是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}");
}
}
并且不小心将 this
与 ref 参数别名化(这是从外部完成的):
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#,您应该像避免瘟疫一样避免使用它们。试想一下,调试代码时会出现来自 class 之外的复杂别名问题。不幸的是,仍然没有任何令人满意的解决方案来解决 C# 中的别名问题。
我来自 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
几乎是一样的,不是const
in C#), 因为我们没有足够的理由通过指针任意修改const值。
进一步探索,我发现在 C# 中没有与 C++ 的 低级常量指针 等价的东西,类似于:
readonly int* p
在 C# 中,如果有的话。
那么p只能读指向的对象,不能写。
对于 const
个对象,C# 禁止尝试检索它们的地址。
在 C++ 中,任何修改 const
的尝试都是编译错误或未定义行为。在C#中,不知道有没有可能利用这个。
所以我的问题是:
- 这种行为真的很明确吗?虽然我知道在 C# 中没有像 C++ 中的 UB 这样的概念
- 如果可以,我该如何使用?或者从不使用它?
注:评论区:cast away const
in C++ 与本题无关,因为只有指向的对象本身不是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}");
}
}
并且不小心将 this
与 ref 参数别名化(这是从外部完成的):
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#,您应该像避免瘟疫一样避免使用它们。试想一下,调试代码时会出现来自 class 之外的复杂别名问题。不幸的是,仍然没有任何令人满意的解决方案来解决 C# 中的别名问题。