Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can pointers be used to modify readonly field? But why?

I come from C++ and find a very different behavior between pointers in C++ and C#.

I surprisingly find this code compiles... And even... Works perfectly.

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

This make me very puzzled, even in C++ we have a mechanism to protect const objects(const in C++ almost the same thing as readonly in C#, not const in C#), because we don't have enough reason to arbitrarily modify const values through pointers.

Exploring further, I find there's no equivalent to C++'s low level const pointers in C#, which will be something like:

readonly int* p

in C# if it had one.

Then p can only read the object pointed to, and cannot write to it.

And for const objects, C# banned the attempts to retrieve their address.

In C++, any attempt to modify the const is either compile error or Undefined Behavior. In C#, I don't know whether there is any possibility that we can make use of this.

So my question is:

  1. Is this behavior really well-defined? Though I know in C# there's no concept like UB in C++
  2. If so, how should I use it? Or never use it?

Note: In comment section: casting away const in C++ is not related to this question, because It's valid only when the pointed to object itself is not const, otherwise it's UB. Plus, I'm basically talking about C# and compile time behavior.

You can ask me to provide more details if you don't fully understand the question. I found most of people cannot understand this correctly, maybe it's my fault not making it clear.

like image 559
con ko Avatar asked Apr 04 '20 02:04

con ko


1 Answers

I wish I could say that you only get this unexpected behaviour because of the unsafe keyword. Unfortunately, there are at least two more ways to change that readonly field, that don't even require unsafe. You can find more about these methods in Joe Duffy's blogpost 'When is a readonly field not readonly'.

By overlapping a field with a non-readonly struct:

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}");
    }
}

And by accidentally aliasing this with a ref parameter (this one is done from outside):

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);
}

All three methods are perfectly well-defined C# that you should avoid like the plague. Just imagine debugging code with complex aliasing problems stemming from outside your class. Unfortunately, there still isn't any satisfactory solution to stop aliasing problems in C#.

like image 79
TamaMcGlinn Avatar answered Nov 14 '22 23:11

TamaMcGlinn