Thread overview
Disagreeing addresses of `this`
May 12
Longinus
May 14
Longinus
May 12

So my understanding is that structs have an implicit opAssign which takes its argument by value, and that it only calls the destructor on its own copy, but not that of the argument, which may be a temporary.

For example:

struct S
{
    this(int x) { writeln("ctor ", &this); }
    ~this()     { writeln("dtor ", &this); }
}
void main()
{
    S s;
    writeln("main ", &s);
    s = S(42);
}

the result, when compiled with DMD, is

main 7FFF28396290
ctor 7FFF28396291
dtor 7FFF28396268
dtor 7FFF28396290

There are three different addresses and the ctors and dtors involved in the copy and opAssign disagree with each other.
Right now this prevents me from using &this in ctors & dtors since, as far as temporaries are concerned, this is as if a dtor call is being elided by the compiler.

There is this bug report about it: https://issues.dlang.org/show_bug.cgi?id=9666
but I'm not sure of whether the differing addresses are part of what's intended.
Are they?

Also are there workarounds to get the ctors and dtors to see the same thing, other than compiling with LDC?

May 12

On Sunday, 12 May 2024 at 13:03:35 UTC, Longinus wrote:

>

So my understanding is that structs have an implicit opAssign which takes its argument by value, and that it only calls the destructor on its own copy, but not that of the argument, which may be a temporary.

Not sure if it helps, but...

This is the low-level code (dmd -vcg-ast):

	ref @system S opAssign(S p) return
	{
		(S __swap40 = void;) , __swap40 = this , (this = p , __swap40.~this());
		return this;
	}
>

For example:

struct S
{
    this(int x) { writeln("ctor ", &this); }
    ~this()     { writeln("dtor ", &this); }
}
void main()
{
    S s;
    writeln("main ", &s);
    s = S(42);
}

the result, when compiled with DMD, is

main 7FFF28396290

main.s

>

ctor 7FFF28396291

Temporary S(42) is constructed on main's stack, passed as argument p to opAssign. Then:

  • main.s is bit-copied to __swap40.
  • p is then bit-copied to main.s.
>

dtor 7FFF28396268

__swap40.~this() inside opAssign.

>

dtor 7FFF28396290

main.s.~this() at the end of main.

May 14

On Sunday, 12 May 2024 at 21:09:33 UTC, Nick Treleaven wrote:

>
  • p is then bit-copied to main.s.

Yeah this is the last time anything is done with p. Its destructor does not get called.

I understand that avoiding this further copy may be an optimisation, but while I can have a dtor deal with an uninitialised object (by doing no-op), an initialised object not having its dtor called, that I don't know how to work with.
My current work-around is to simply not use temporaries, but it's awkward and I don't know how to make the compiler warn on their usage.
(For reference: yes, I'm literally having the ctor/dtor write &this in places; so this is introducing dangling pointers)

Thanks for looking into it.

May 14

On Tuesday, 14 May 2024 at 02:01:10 UTC, Longinus wrote:

>

On Sunday, 12 May 2024 at 21:09:33 UTC, Nick Treleaven wrote:

>
  • p is then bit-copied to main.s.

Yeah this is the last time anything is done with p. Its destructor does not get called.

I understand that avoiding this further copy may be an optimisation, but while I can have a dtor deal with an uninitialised object (by doing no-op), an initialised object not having its dtor called, that I don't know how to work with.

But the destructor does get called on each initialized object. s is moved (not copied) into the uninitialized __swap40 and then the destructor is called on __swap40. Then p is moved into s and the destructor is called on s at main's exit. Note that D mandates all struct objects be movable.