www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Why would an initialised struct pointer field be null in the struct's

reply Gary Willoughby <dev nomad.so> writes:
In the following code, the `_foo` pointer (of the Foo struct) is 
null in the first call to the destructor. Why is this? I think 
it's got something to do with the foreach loop but I'm not sure. 
Any ideas?


import std.stdio;
import core.stdc.stdlib : malloc, calloc, free;

struct Foo
{
	public int* _foo;

	public this(int n)
	{
		this._foo  = cast(int*) malloc(int.sizeof);
		writefln("ctor: %x", this._foo);
	}

	public this(this)
	{
		writefln("post blit: %x", this._foo);
	}

	public ~this()
	{
		// Why is this._foo null here???
		writefln("dtor: %x", this._foo);
	}
}

struct Bar
{
	private Foo[] _data;

	public this(int n)
	{
		this._data = (cast(Foo*) calloc(n, Foo.sizeof))[0 .. n];

		foreach(ref element; this._data)
		{
			auto tmp = Foo(1);
			element = tmp;
		}
	}
}

void main(string[] args)
{
	auto bar = Bar(1);
}
May 20 2017
next sibling parent reply Moritz Maxeiner <moritz ucworks.org> writes:
On Saturday, 20 May 2017 at 10:48:54 UTC, Gary Willoughby wrote:
 In the following code, the `_foo` pointer (of the Foo struct) 
 is null in the first call to the destructor. Why is this? I 
 think it's got something to do with the foreach loop but I'm 
 not sure. Any ideas?

 struct Bar
 {
 	private Foo[] _data;

 	public this(int n)
 	{
 		this._data = (cast(Foo*) calloc(n, Foo.sizeof))[0 .. n];

 		foreach(ref element; this._data)
 		{
 			auto tmp = Foo(1);
 			element = tmp;
 		}
 	}
 }
Because `element = tmp` destroys `element`, which you allocated filled with zeroes. The destructor will run for each `element`.
May 20 2017
parent Gary Willoughby <dev nomad.so> writes:
On Saturday, 20 May 2017 at 11:15:57 UTC, Moritz Maxeiner wrote:
 Because `element = tmp` destroys `element`, which you allocated 
 filled with zeroes.
 The destructor will run for each `element`.
Right, I get it because the destructors running on the struct that is being replaced. Doh!
May 20 2017
prev sibling next sibling parent reply Stanislav Blinov <stanislav.blinov gmail.com> writes:
On Saturday, 20 May 2017 at 10:48:54 UTC, Gary Willoughby wrote:
 In the following code, the `_foo` pointer (of the Foo struct) 
 is null in the first call to the destructor. Why is this? I 
 think it's got something to do with the foreach loop but I'm 
 not sure. Any ideas?
Oof. Dangerous stuff. As Moritz pointed out, default opAssign will call the destructor on Foo. I should, however, elaborate further. You're dealing with uninitialized data. calloc() gives you zero-initialized block, which is not necessarily how Foo.init would look (it does in this case, but that's not true in general case). malloc() gives you a potentially garbage-filled block, which is even more dangerous. When filling an array of uninitialized values, use std.algorithm.moveEmplace(src, dst) instead of assignment. And, if you have destructors, you have to call the destructors manually when you free the array. Furthermore, since Bar._data is an array of Foos, and Foo has a pointer in it, you might want to register the Bar._data array with the GC (GC.addRange, GC.removeRange). Unless you're willing to manually make sure that GC *never* sees those pointers.
May 20 2017
parent Gary Willoughby <dev nomad.so> writes:
On Saturday, 20 May 2017 at 12:25:39 UTC, Stanislav Blinov wrote:
 Oof. Dangerous stuff.
:) Thanks for the heads up but I think I'm covering all cases in my main code.
May 20 2017
prev sibling next sibling parent Eduard Staniloiu <edi33416 gmail.com> writes:
On Saturday, 20 May 2017 at 10:48:54 UTC, Gary Willoughby wrote:
Looks like you would want to use emplace [0] here.

 	public this(int n)
 	{
 		this._data = (cast(Foo*) calloc(n, Foo.sizeof))[0 .. n];

 		foreach(ref element; this._data)
 		{
 			auto tmp = Foo(1);
 			element = tmp;
 		}
 	}
Cheers, Eduard [0] - https://dlang.org/phobos/std_conv.html#.emplace.3
May 21 2017
prev sibling parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Saturday, 20 May 2017 at 10:48:54 UTC, Gary Willoughby wrote:
 		// Why is this._foo null here???
The others have answered why and what to do, but note that according to the spec, that any struct should be able to have its destructor called, so you should do a null check in there anyway.
May 21 2017
next sibling parent reply Stanislav Blinov <stanislav.blinov gmail.com> writes:
On Sunday, 21 May 2017 at 12:48:10 UTC, Adam D. Ruppe wrote:
 On Saturday, 20 May 2017 at 10:48:54 UTC, Gary Willoughby wrote:
 		// Why is this._foo null here???
The others have answered why and what to do, but note that according to the spec, that any struct should be able to have its destructor called, so you should do a null check in there anyway.
Not if you either emplace() or blit Foo.init into all of the array elements.
May 21 2017
parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Sunday, 21 May 2017 at 14:13:20 UTC, Stanislav Blinov wrote:
 Not if you either emplace() or blit Foo.init into all of the 
 array elements.
You especially need to be safe calling ~this on Foo.init.
May 21 2017
parent reply Stanislav Blinov <stanislav.blinov gmail.com> writes:
On Monday, 22 May 2017 at 00:23:26 UTC, Adam D. Ruppe wrote:
 On Sunday, 21 May 2017 at 14:13:20 UTC, Stanislav Blinov wrote:
 Not if you either emplace() or blit Foo.init into all of the 
 array elements.
You especially need to be safe calling ~this on Foo.init.
How so? .init is supposed to be destructible without question. destroy() calls in the runtime also blit the initializer back over the destructed objects. std.algorithm.move et al. specifically take advantage of .init (blit it over the moved-from object, so it can either be destructed or assigned something else). I can't think of any case where you'd want preconditions on destructor when the object is in .init state.
May 21 2017
parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Monday, 22 May 2017 at 00:36:24 UTC, Stanislav Blinov wrote:
 I can't think of any case where you'd want preconditions on 
 destructor when the object is in .init state.
I think we're actually saying the same thing: I mean the destructor must be callable on .init so you might do like struct Foo { int* ptr; ~this() { // might be called with ptr is null cuz of .init // so wanna check if(ptr !is null) free(ptr); } }
May 21 2017
parent Stanislav Blinov <stanislav.blinov gmail.com> writes:
On Monday, 22 May 2017 at 00:45:27 UTC, Adam D. Ruppe wrote:
 On Monday, 22 May 2017 at 00:36:24 UTC, Stanislav Blinov wrote:
 I can't think of any case where you'd want preconditions on 
 destructor when the object is in .init state.
I think we're actually saying the same thing: I mean the destructor must be callable on .init so you might do like struct Foo { int* ptr; ~this() { // might be called with ptr is null cuz of .init // so wanna check if(ptr !is null) free(ptr); } }
Ah, yes, exactly. The page is indeed the same one. P.S. Though it's fine to call free with a null pointer :P
May 21 2017
prev sibling parent reply Guillaume Piolat <first.last gmail.com> writes:
On Sunday, 21 May 2017 at 12:48:10 UTC, Adam D. Ruppe wrote:
 Any struct should be able to have its destructor called
Does this rule also applies to class objects?
May 21 2017
parent Stanislav Blinov <stanislav.blinov gmail.com> writes:
On Sunday, 21 May 2017 at 23:59:08 UTC, Guillaume Piolat wrote:
 On Sunday, 21 May 2017 at 12:48:10 UTC, Adam D. Ruppe wrote:
 Any struct should be able to have its destructor called
Does this rule also applies to class objects?
Yes. If your destructor does modify the state, you should expect it to be called and have the state ready for it. When you're using the GC, destructors *may* not be called under certain conditions: http://dlang.org/spec/class.html#destructors But there's no stopping you from destructing manually (via destroy() call), or by allocating classes manually via malloc or on the stack.
May 21 2017