July 22, 2015
On Wednesday, 22 July 2015 at 09:04:49 UTC, Marc Schütz wrote:
>
> But as long as the original pointer is still on the stack, that one _will_ keep the object alive. It is only a problem if all pointers to a GC managed object are stored in places the GC isn't informed about.

Sorry, I have gotten confused. In Ali's example, the pointer to a class object (via the address-of '&' operator) actually points into the GC heap. It is *not* a pointer to a pointer, right?

My reading of the Garbage web doc page is that this pointer to memory in the GC heap is sufficient (by some magic) to keep the memory alive, in and of itself.

So the pointer, passed to the other thread is sufficient to keep the memory alive, even if the original reference disappears.

Or, to put it another way, getting threads out of the equation, is this safe?

  class MyThing { ... }

  MyThing* create_a_thing() {
    MyThing mt = new MyThing();
    do_something_with(mt);
    return &mt;
  }

  void main() {
    MyThing* pmt = create_a_thing();
    // ...
  }

The "thing" will remain alive for the duration of main() ??

Thanks

July 22, 2015
On Wednesday, 22 July 2015 at 17:17:17 UTC, Frank Pagliughi wrote:
> On Wednesday, 22 July 2015 at 09:04:49 UTC, Marc Schütz wrote:
>>
>> But as long as the original pointer is still on the stack, that one _will_ keep the object alive. It is only a problem if all pointers to a GC managed object are stored in places the GC isn't informed about.
>
> Sorry, I have gotten confused. In Ali's example, the pointer to a class object (via the address-of '&' operator) actually points into the GC heap. It is *not* a pointer to a pointer, right?
>
> My reading of the Garbage web doc page is that this pointer to memory in the GC heap is sufficient (by some magic) to keep the memory alive, in and of itself.
>
> So the pointer, passed to the other thread is sufficient to keep the memory alive, even if the original reference disappears.
>
> Or, to put it another way, getting threads out of the equation, is this safe?
>
>   class MyThing { ... }
>
>   MyThing* create_a_thing() {
>     MyThing mt = new MyThing();
>     do_something_with(mt);
>     return &mt;
>   }
>
>   void main() {
>     MyThing* pmt = create_a_thing();
>     // ...
>   }
>
> The "thing" will remain alive for the duration of main() ??
>
> Thanks

No.
this is actually returning an address of a temporary.
July 23, 2015
On Wednesday, 22 July 2015 at 17:17:17 UTC, Frank Pagliughi wrote:
> Or, to put it another way, getting threads out of the equation, is this safe?
>
>   class MyThing { ... }
>
>   MyThing* create_a_thing() {
>     MyThing mt = new MyThing();
>     do_something_with(mt);
>     return &mt;
>   }
>
>   void main() {
>     MyThing* pmt = create_a_thing();
>     // ...
>   }
>
> The "thing" will remain alive for the duration of main() ??

It is not safe, but for a different reason: `mt` is already a _reference_ to the actual object (that's how classes behave in D). This reference is located in a register or on the stack, and `&mt` is therefore a pointer into the stack.

It's illegal to return that pointer from the function, because it will become invalid once the function is left. Fortunately, the compiler can detect simple cases like this one, and will refuse to compile it:

    Object* foo() {
        Object o;
        return &o;
    }

xx.d(3): Error: escaping reference to local o
July 23, 2015
On Thursday, 23 July 2015 at 09:05:12 UTC, Marc Schütz wrote:
>
> It is not safe, but for a different reason: `mt` is already a _reference_ to the actual object (that's how classes behave in D). This reference is located in a register or on the stack, and `&mt` is therefore a pointer into the stack.
>
> It's illegal to return that pointer from the function, because it will become invalid once the function is left. Fortunately, the compiler can detect simple cases like this one, and will refuse to compile it:
>
>     Object* foo() {
>         Object o;
>         return &o;
>     }
>
> xx.d(3): Error: escaping reference to local o

Very interesting. You see, I am trying to resolve the distinction be a value type and a reference type in D.

If Object were declared as a "struct", this would make sense to me. The object would be created on the stack as a temporary, and it would disappear when the function exited. So returning a pointer to it would be a very, very bad thing.

But a class object is allocated in the GC heap. I would have guessed that, since a class reference is just essentially a hidden pointer, that the address-of operator '&' for a class object would return the address into the heap... not the address of the reference itself! Just a little syntactic sugar.

But that's not the case. I thought this was true:
  class MyThing { ... };

  MyThing a = new MyThing,
          b = a;
  assert(&a == &b);  // Fails

In a weird way, that makes total sense to me, and no sense at all.

So, passing a pointer to a stack-based reference from one thread is another is not necessarily a good thing to do, as the original reference might disappear while the thread is using it.

Is there a way to get the address of the actual heap object from a class reference? Or am I drifting to far into the realm of "unsafe".

This all goes back to my original question of passing an immutable object from one thread to another. It is simple with arrays, since there is a clear distinction between the array reference and its contents. You can easily create a mutable reference to immutable contents with an array.

But it seems rather convoluted with class objects.

So, in the end, it seems best to send a rebindable reference to the other thread, and perhaps hide the mild ugliness behind a library API that takes an immutable object and then sends the rebindable version, like:

  void send_message(Tid tid, immutable(Message) msg) {
    send(tid, thisTid(), rebindable(msg));
  }

That seems easy enough.

Thanks much for all the help.

July 24, 2015
On 07/23/2015 06:48 AM, Frank Pagliughi wrote:

> So, passing a pointer to a stack-based reference from one thread is
> another is not necessarily a good thing to do, as the original reference
> might disappear while the thread is using it.

Right.

> Is there a way to get the address of the actual heap object from a class
> reference?

It is possible by casting the reference to a pointer type:

import std.stdio;

class B
{
    int i;
    this(int i) { this.i = i; }
}

class D : B
{
    int j;
    this (int j) {
        super(j);
        this.j = j + 1;
    }
}

void main()
{
    auto r = new D(42);
    writefln("Address of reference: %s", &r);
    writefln("Address of object   : %s", cast(void*)r);
    writefln("Address of i        : %s", &r.i);
    writefln("Address of j        : %s", &r.j);

    auto p = cast(void*)r;
    auto r2 = cast(D)p;
    writefln("i through another reference: %s", r2.i);
    writefln("j through another reference: %s", r2.j);
}

Although the example casts to void*, ubyte* and others are possible as well, and casting back to the correct class type seems to work:

Address of reference: 7FFFCB137580
Address of object   : 7FFCB9407520
Address of i        : 7FFCB9407530
Address of j        : 7FFCB9407534
i through another reference: 42
j through another reference: 43

Ali

July 24, 2015
On Friday, 24 July 2015 at 18:02:58 UTC, Ali Çehreli wrote:
> Although the example casts to void*, ubyte* and others are possible as well, and casting back to the correct class type seems to work:

Thanks, Ali.

I just tried a few things, and apparently, you don't need to go to a different type or void. You can make it a pointer the the actual type, which might be good for self-documentation or pattern matching. But what I find somewhat odd and fascinating is what you show, that for the reference, "r":

  cast(D*)r != &r

So this code all works out:

  import std.stdio;

  class B
  {
    int i;
    this(int i) { this.i = i; }
  }

  void main()
  {
    auto r1 = new B(42),
         r2 = r1;

    writefln("Address of reference r1: %s", &r1);
    writefln("Address of reference r2: %s", &r2);

    writefln("Address of object r1   : %s", cast(B*)r1);
    writefln("Address of object r2   : %s", cast(B*)r2);

    assert(cast(B*)r1 == cast(B*)r2);

    assert(cast(B*)r1 != &r1);
    assert(cast(B*)r2 != &r2);
  }

and prints:

  Address of reference r1: 7FFF1D4E4C70
  Address of reference r2: 7FFF1D4E4C78
  Address of object r1   : 7F01CD506000
  Address of object r2   : 7F01CD506000


So then, of course, I hope/wonder/assume that the pointer to the heap is sufficient to keep the heap memory alive, and that this would be OK from the GC perspective to do something like this:

  B* make_b_thing(int i) { cast(B*) new B(i); }

That seems to work, but I guess I should try to force the garbage collector to run to see if I can crash the program.

***BUT***: The really, really weird thing is that even though you *think* that you have a pointer to a B object, you don't really. Dereferencing is accepted by the compiler, but it plays a nasty trick on you:

  B* p = make_b_thing(42);
  writefln("Address of pointer: %s", p);

  writefln("Value of i: %s", p.i);
  writefln("Value of i: %s", (*p).i);
  writefln("Value of i: %s", (cast(B)p).i);

This compiles and runs fine, but produces:

  Address of pointer: 7F7EE77CF020
  Value of i: 4445040
  Value of i: 4445040
  Value of i: 42

Maybe it's my C++ background talking, but that seems a bit counter-intuitive.
July 24, 2015
On Friday, 24 July 2015 at 18:55:26 UTC, Frank Pagliughi wrote:
> So then, of course, I hope/wonder/assume that the pointer to the heap is sufficient to keep the heap memory alive, and that this would be OK from the GC perspective to do something like this:
>
>   B* make_b_thing(int i) { cast(B*) new B(i); }

(You missed a return there.)

I haven't followed the discussion, so I may be missing the point here. But if you're doing this so that the GC is aware of the `new`ed B, then you really don't need to. The GC has no problems with class types. Class references do keep objects alive.

That is, the above will not keep the created object any more alive than the following:

B make_b_thing(int i) { return new B(i); }

>
> That seems to work, but I guess I should try to force the garbage collector to run to see if I can crash the program.
>
> ***BUT***: The really, really weird thing is that even though you *think* that you have a pointer to a B object, you don't really. Dereferencing is accepted by the compiler, but it plays a nasty trick on you:

A B* is not a pointer to the memory of the object. It's a pointer to a class reference. The class reference itself, B, is a pointer to the memory of the object, under the hood.

Casting a B to B* makes as little sense as casting it to float* or char*.

>
>   B* p = make_b_thing(42);
>   writefln("Address of pointer: %s", p);
>
>   writefln("Value of i: %s", p.i);

p really is a pointer to the memory of the object. But by typing it B* you're stating that it's a pointer to a class reference, which is wrong.

p.i does two dereferences where one would be correct. First, p is dereferenced. The resulting data is really that of the object. But it's typed B, so the compiler thinks it's a class reference. So it takes first bytes of the actual object data, interprets it as a pointer, dereferences it, and assumes to see the object data there (wherever that is). Getting the i field of that garbage location results in some garbage data, of course.

>   writefln("Value of i: %s", (*p).i);

Same as above. When p is a pointer, then p.i becomes (*p).i automatically in D.

>   writefln("Value of i: %s", (cast(B)p).i);

Here the type has only one level of indirection, as it should be. And everything's fine.

>
> This compiles and runs fine, but produces:
>
>   Address of pointer: 7F7EE77CF020
>   Value of i: 4445040
>   Value of i: 4445040
>   Value of i: 42
>
> Maybe it's my C++ background talking, but that seems a bit counter-intuitive.

I'd say it's your C++ background talking.
July 24, 2015
On Friday, 24 July 2015 at 19:28:35 UTC, anonymous wrote:
> I haven't followed the discussion, so I may be missing the point here.

I started by asking how to send a reference to an immutable class object from one thread to another if the reference is one of several parameters being sent. The concurrency library can't make a tuple for send/receive if the reference is immutable. The short answer is (probably) to use a rebindable reference, but a suggestion arose about the possible use of a pointer instead. So we've devolved into a discussion of how pointers to class objects work, and how to keep the heap memory alive as the pointer is sent to the second thread as the original reference goes out of scope in the first.

> A B* is not a pointer to the memory of the object. It's a pointer to a class reference. The class reference itself, B, is a pointer to the memory of the object, under the hood.

Hahaha. My forehead is red from the number of times I've thought I've "gotten it" in this discussion.

So I think I understand that when you start to peek under the hood, the language treats the reference and the heap memory as distinct entities, and that they work differently for struct's and classes.

So then: is there a pointer notation to which you can cast the "B" reference, which thus points to the heap, but retains type identity of the heap object?

And the reason I ask if I wanted to declare a type which is a mutable pointer to an immutable object that is on the GC heap, keeping type info? Or is the answer, "no", just use Rebindable when necessary?

At this point, I'm not asking what "should" I do, but what "could" I do.

July 25, 2015
On Friday, 24 July 2015 at 21:51:44 UTC, Frank Pagliughi wrote:
> So then: is there a pointer notation to which you can cast the "B" reference, which thus points to the heap, but retains type identity of the heap object?

There's no straight forward way to do that. D has no types for the actual objects of classes. You'd have to generate a new struct type from B that you can point at.

>
> And the reason I ask if I wanted to declare a type which is a mutable pointer to an immutable object that is on the GC heap, keeping type info? Or is the answer, "no", just use Rebindable when necessary?

We only have Rebindable because there is no nice way in the language to do it. So, just use Rebindable, yes.

You could look at what Rebindable does, and then do that, of course. But be sure that you understand all the nasty details.
1 2
Next ›   Last »