Unexpected aliasing

Jonathan M Davis newsgroup.d at jmdavisprog.com
Mon Nov 11 21:52:12 UTC 2019


On Monday, November 11, 2019 12:17:37 PM MST Bastiaan Veelo via Digitalmars-
d-learn wrote:
> Recently I got my first surprise with our use of D. The symptom
> was that two local variables in two different functions appeared
> to be sharing data.
>
> A simplified example is shown below (the original was machine
> translated from Pascal and involved templates and various levels
> of indirection). What I did not know is that the initial value of
> struct members is a compile time feature, apparently. What I
> suspect is happening is that the array lives in the static data
> segment (or is created in the module constructor?) and that the
> slices inside arr1 and arr2 get initialised to point to that same
> array.
>
> I could use some help in rewriting the code below so that arr1
> and arr2 each have their own data; ideally with minimal changes
> so that I can make the transcompiler do the right thing.
>
> Thanks!
> Bastiaan.
>
> void main()
> {
>   import std.stdio;
>
>   WrapIntegerArray arr1;
>   arr1[0] = 42;
>
>   WrapIntegerArray arr2;
>
>   writeln(arr2[0]); // 42, not 0.
>   writeln("arr1.wrap.arr.ptr = ", arr1.wrap.arr.ptr);
>   writeln("arr2.wrap.arr.ptr = ", arr2.wrap.arr.ptr); // identical
>   assert(arr2[0] == 0); // fails
> }
>
> struct IntegerArray
> {
>   int[] arr;
>   alias arr this;
>   this(int l)
>   {
>       arr = new int[l];
>   }
> }
>
> struct WrapIntegerArray
> {
>   auto wrap = IntegerArray(5); // This is CTFE! :-(
>   alias wrap this;
> }

All struct and class members which are directly initialized must have their
values known at compile-time. For structs, that's what goes in the init
value for the type. A side effect of this is that it's usually a bad idea to
directly initialize dynamic arrays which are member variables. You need to
do the initialization in a constructor. And for structs, if you need a
no-arg constructor, then you'll need to use a factory function (since
structs can't have no-arg constructors). e.g.

struct WrapIntegerArray
{
    IntegerArray wrap;
    alias wrap this;

    this(int len)
    {
        wrap = IntegerArray(len);
    }
}

or

struct WrapIntegerArray
{
    IntegerArray wrap;
    alias wrap this;

    static make()
    {
        WrapIntegerArray retval;
        retval.wrap = IntegerArray(5);
        return retval;
    }
}

So, you could then have something like

    auto arr1 = WrapIntegerArray(5);
    arr1[0] = 42;

    or

    auto arr1 = WrapIntegerArray.make();
    arr1[0] = 42;

but if you use the init value (which is what you get if you let the type be
default-initialized), then you'll have to first do something to allocate the
dynamic array if you want to be able to index it, since if you don't give it
a value at compile-time, it's null (and you don't want to give it a value at
compile-time, because then every default-initialized struct of that type
will refer to the same dynamic array). Of course, you could always just
append values, and the dynamic array will be allocated and grow accordingly,
but that's obviously not the same as allocating it up front to have a
specific length.

- Jonathan M Davis





More information about the Digitalmars-d-learn mailing list