Thread overview | ||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
June 16, 2020 what exactly does cast(shared) and cast away shared do? | ||||
---|---|---|---|---|
| ||||
I'm trying this program: ----------------------------------------------------------------------------- class Foo { int attr; this() { writeln("Foo.ctor:", &attr); } } void threadInc(shared Foo foo) { writeln("threadInc:", &foo); writeln("threadInc:", &(foo.attr)); Foo f = cast(Foo)foo; writeln("threadInc:", &f); foo.attr = 123; } void main() { Foo foo = new Foo(); writeln("main:\t", &foo); writeln("main:\t", &(foo.attr)); spawn(&threadInc, cast(shared)foo); // cannot pass foo: Error: static assert: "Aliases to mutable thread-local data not allowed." Have to cast to shared thread_joinAll(); writeln("main:", foo.attr); writeln("main:\t", &foo); writeln("main:\t", &(foo.attr)); shared sf1 = cast(shared)foo; shared sf2 = cast(shared)foo; writeln("sf1:\t", &sf1, "\t", &(sf1.attr)); writeln("sf2:\t", &sf2, "\t", &(sf2.attr)); foo.attr = 456; writeln("main:", foo.attr); writeln("main:", sf1.attr); writeln("main:", sf2.attr); } ----------------------------------------------------------------------------- here is the result: ----------------------------------------------------------------------------- Foo.ctor:7FB83F4B0010 <- &attr is always the same main: 7FFFD6D081F0 <- &foo main: 7FB83F4B0010 threadInc:7FB83E1EFC08 <- cast(shared)foo threadInc:7FB83F4B0010 threadInc:7FB83E1EFBF8 <- cast(Foo)foo, but != the original &foo main:123 main: 7FFFD6D081F0 <- foo main: 7FB83F4B0010 sf1: 7FFFD6D08200 7FB83F4B0010 <- each time cast(shared)foo will have a new addr sf2: 7FFFD6D08208 7FB83F4B0010 <- but &(x.attr) is always the same main:456 main:456 main:456 ----------------------------------------------------------------------------- In all the 3 cases, &(foo.attr) are the same; this is what I wanted, since the object is allocated on the heap. But the memory address of the original foo, cast(shared) foo, and cast(Foo) foo are all different. And for sf1, sf2, each time cast(shared)foo will have a new addr. So every time cast(shared) / cast away shared will create a new wrapper to the original object, but all these wrappers' actual fields (the physical addr) stay the same? What's the point of doing these wrappers every time? Esp. for sf1, and sf2? `shared` means it's globally *shared*, everywhere it's the same object, then why we have &(sf1) != &(sf2)? What's the exact semantics of cast(shared) and cast away shared? Thanks. |
June 16, 2020 Re: what exactly does cast(shared) and cast away shared do? | ||||
---|---|---|---|---|
| ||||
Posted in reply to mw | On Tuesday, 16 June 2020 at 06:15:11 UTC, mw wrote:
> I'm trying this program:
>
> -----------------------------------------------------------------------------
> class Foo {
> int attr;
>
> this() {
> writeln("Foo.ctor:", &attr);
> }
> }
>
> void threadInc(shared Foo foo) {
> writeln("threadInc:", &foo);
> writeln("threadInc:", &(foo.attr));
> Foo f = cast(Foo)foo;
> writeln("threadInc:", &f);
> foo.attr = 123;
> }
>
> void main() {
> Foo foo = new Foo();
> writeln("main:\t", &foo);
> writeln("main:\t", &(foo.attr));
>
> spawn(&threadInc, cast(shared)foo); // cannot pass foo: Error: static assert: "Aliases to mutable thread-local data not allowed." Have to cast to shared
> thread_joinAll();
>
> writeln("main:", foo.attr);
> writeln("main:\t", &foo);
> writeln("main:\t", &(foo.attr));
>
> shared sf1 = cast(shared)foo;
> shared sf2 = cast(shared)foo;
> writeln("sf1:\t", &sf1, "\t", &(sf1.attr));
> writeln("sf2:\t", &sf2, "\t", &(sf2.attr));
>
> foo.attr = 456;
> writeln("main:", foo.attr);
> writeln("main:", sf1.attr);
> writeln("main:", sf2.attr);
> }
> -----------------------------------------------------------------------------
>
> here is the result:
>
> -----------------------------------------------------------------------------
> Foo.ctor:7FB83F4B0010 <- &attr is always the same
> main: 7FFFD6D081F0 <- &foo
> main: 7FB83F4B0010
> threadInc:7FB83E1EFC08 <- cast(shared)foo
> threadInc:7FB83F4B0010
> threadInc:7FB83E1EFBF8 <- cast(Foo)foo, but != the original &foo
> main:123
> main: 7FFFD6D081F0 <- foo
> main: 7FB83F4B0010
> sf1: 7FFFD6D08200 7FB83F4B0010 <- each time cast(shared)foo will have a new addr
> sf2: 7FFFD6D08208 7FB83F4B0010 <- but &(x.attr) is always the same
> main:456
> main:456
> main:456
> -----------------------------------------------------------------------------
>
> In all the 3 cases, &(foo.attr) are the same; this is what I wanted, since the object is allocated on the heap.
>
> But the memory address of the original foo, cast(shared) foo, and cast(Foo) foo are all different.
>
> And for sf1, sf2, each time cast(shared)foo will have a new addr.
>
> So every time cast(shared) / cast away shared will create a new wrapper to the original object, but all these wrappers' actual fields (the physical addr) stay the same?
>
> What's the point of doing these wrappers every time?
>
> Esp. for sf1, and sf2? `shared` means it's globally *shared*, everywhere it's the same object, then why we have &(sf1) != &(sf2)?
>
>
> What's the exact semantics of cast(shared) and cast away shared?
The only thing cast(shared) does is tell the type system 'this object is shared'. The reason you're getting different addresses is you're taking the address of the reference on the stack. As you've noticed, &foo.attr, &(sf1.attr), &(sf2.attr) is exactly the same, so the actual instances are the same.
So in D, unlike C++, you can't really refer to a class instance directly - you always have a reference to the instance instead. When you have 'Foo instance = new Foo();', 'instance' is, behind the scenes, a pointer, for instance 7FB83F4B0000. When you further add 'Foo instance2 = instance;', they both refer to the same object. However, 'instance' and 'instance2' are themselves separate variables, placed on the stack, and they will have their own addresses, which is what you get with &instance and &instance2. If you look at the addresses you get for &foo vs &(foo.attr), you will notice they are significantly different, which you would not expect for a pointer to the instance vs a pointer to a field inside that instance, unless the instance was humongous.
Since I've told you class references are pointers behind the scenes, you can print their values by casting them to to some kind of pointer. Try replacing &foo in the code with cast(int*)foo, and you'll see they all point to the same memory (8 or 16 bytes before the address of attr, since class instances have some hidden fields - vtable and monitor).
--
Simen
|
June 16, 2020 Re: what exactly does cast(shared) and cast away shared do? | ||||
---|---|---|---|---|
| ||||
Posted in reply to mw | Am 16.06.20 um 08:15 schrieb mw:
> I'm trying this program:
>
> -----------------------------------------------------------------------------
>
> class Foo {
> int attr;
>
> this() {
> writeln("Foo.ctor:", &attr);
> }
> }
>
> void threadInc(shared Foo foo) {
> writeln("threadInc:", &foo);
> writeln("threadInc:", &(foo.attr));
> Foo f = cast(Foo)foo;
> writeln("threadInc:", &f);
> foo.attr = 123;
> }
>
> void main() {
> Foo foo = new Foo();
> writeln("main:\t", &foo);
> writeln("main:\t", &(foo.attr));
>
> spawn(&threadInc, cast(shared)foo); // cannot pass foo: Error: static
> assert: "Aliases to mutable thread-local data not allowed." Have to
> cast to shared
> thread_joinAll();
>
> writeln("main:", foo.attr);
> writeln("main:\t", &foo);
> writeln("main:\t", &(foo.attr));
>
> shared sf1 = cast(shared)foo;
> shared sf2 = cast(shared)foo;
> writeln("sf1:\t", &sf1, "\t", &(sf1.attr));
> writeln("sf2:\t", &sf2, "\t", &(sf2.attr));
>
> foo.attr = 456;
> writeln("main:", foo.attr);
> writeln("main:", sf1.attr);
> writeln("main:", sf2.attr);
> }
> -----------------------------------------------------------------------------
>
>
> here is the result:
>
> -----------------------------------------------------------------------------
>
> Foo.ctor:7FB83F4B0010 <- &attr is always the same
> main: 7FFFD6D081F0 <- &foo
> main: 7FB83F4B0010
> threadInc:7FB83E1EFC08 <- cast(shared)foo
> threadInc:7FB83F4B0010
> threadInc:7FB83E1EFBF8 <- cast(Foo)foo, but != the original
> &foo
> main:123
> main: 7FFFD6D081F0 <- foo
> main: 7FB83F4B0010
> sf1: 7FFFD6D08200 7FB83F4B0010 <- each time cast(shared)foo will
> have a new addr
> sf2: 7FFFD6D08208 7FB83F4B0010 <- but &(x.attr) is always the same
> main:456
> main:456
> main:456
> -----------------------------------------------------------------------------
>
>
> In all the 3 cases, &(foo.attr) are the same; this is what I wanted, since the object is allocated on the heap.
>
> But the memory address of the original foo, cast(shared) foo, and
> cast(Foo) foo are all different.
>
> And for sf1, sf2, each time cast(shared)foo will have a new addr.
>
> So every time cast(shared) / cast away shared will create a new wrapper
> to the original object, but all these wrappers' actual fields (the
> physical addr) stay the same?
>
> What's the point of doing these wrappers every time?
>
> Esp. for sf1, and sf2? `shared` means it's globally *shared*,
> everywhere it's the same object, then why we have &(sf1) != &(sf2)?
>
>
> What's the exact semantics of cast(shared) and cast away shared?
>
>
> Thanks.
>
This is because you are using different class variables and then take their address. Of course the variables themselves have different addresses on the stack, which is why you see different addresses. But as you already noticed, they point to the same object. You get the exact same behavior when using several regular variables (i.e. no shared involved) that point to the same class object.
```
import std;
class A {}
void main()
{
auto a = new A();
auto b = a;
writeln(&a); // 7FFF0D776E20
writeln(&b); // 7FFF0D776E28
}
```
Classes are reference types, i.e. internally, they actually are pointers. you can cast them to void* to see what they actually point to:
```
import std;
class A {}
void main()
{
A a = new A();
A b = a;
shared(A) c = cast(shared A) a;
A d = cast(A) c;
writeln(&a); // 7FFF0D776E20
writeln(&b); // 7FFF0D776E28
writeln(&c); // 7FFE15993F80
writeln(&d); // 7FFE15993F88
writeln(cast(void*)a); // 7FD1AB5AA000
writeln(cast(void*)b); // 7FD1AB5AA000
writeln(cast(void*)c); // 7FD1AB5AA000
writeln(cast(void*)d); // 7FD1AB5AA000
}
```
As you see, casting to shared and back inbetween doesn't have any effect on the object the variables actually points to.
|
June 16, 2020 Re: what exactly does cast(shared) and cast away shared do? | ||||
---|---|---|---|---|
| ||||
Posted in reply to Simen Kjærås | On Tuesday, 16 June 2020 at 06:53:22 UTC, Simen Kjærås wrote:
>> What's the exact semantics of cast(shared) and cast away shared?
>
> The only thing cast(shared) does is tell the type system 'this object is shared'. The reason you're getting different addresses is you're taking the address of the reference on the stack. As you've noticed, &foo.attr, &(sf1.attr), &(sf2.attr) is exactly the same, so the actual instances are the same.
Thanks for the explanation. I've thought &foo will return the address of that actual object, just as &(foo.attr) does for .attr.
So cast(shared) and cast away shared only affect the type system, the actual object will always stay the same, and there is not any magic operation or wrapper behind the scene.
`shared` or not is purely a compile-time attribute, not some runtime dynamic attribute on the object, i.e. at runtime when you get hold of an object on the heap, there's no way you can tell from the object itself whether it's shared or not.
|
June 16, 2020 Re: what exactly does cast(shared) and cast away shared do? | ||||
---|---|---|---|---|
| ||||
Posted in reply to mw | On Tuesday, 16 June 2020 at 07:22:59 UTC, mw wrote: > > Thanks for the explanation. I've thought &foo will return the address of that actual object, just as &(foo.attr) does for .attr. To get the actual address, you need to cast the class reference to a pointer: https://run.dlang.io/gist/run-dlang/ea2b163f0fd90e16adc72ab78ed48553?compiler=dmd > So cast(shared) and cast away shared only affect the type system, the actual object will always stay the same, and there is not any magic operation or wrapper behind the scene. > > `shared` or not is purely a compile-time attribute, not some runtime dynamic attribute on the object, i.e. at runtime when you get hold of an object on the heap, there's no way you can tell from the object itself whether it's shared or not. Yes, exactly. |
June 16, 2020 Re: what exactly does cast(shared) and cast away shared do? | ||||
---|---|---|---|---|
| ||||
Posted in reply to mw | On Tuesday, 16 June 2020 at 07:22:59 UTC, mw wrote: > On Tuesday, 16 June 2020 at 06:53:22 UTC, Simen Kjærås wrote: >>> What's the exact semantics of cast(shared) and cast away shared? >> >> The only thing cast(shared) does is tell the type system 'this object is shared'. The reason you're getting different addresses is you're taking the address of the reference on the stack. As you've noticed, &foo.attr, &(sf1.attr), &(sf2.attr) is exactly the same, so the actual instances are the same. > > Thanks for the explanation. I've thought &foo will return the address of that actual object, just as &(foo.attr) does for .attr. > > So cast(shared) and cast away shared only affect the type system, the actual object will always stay the same, and there is not any magic operation or wrapper behind the scene. > > `shared` or not is purely a compile-time attribute, not some runtime dynamic attribute on the object, i.e. at runtime when you get hold of an object on the heap, there's no way you can tell from the object itself whether it's shared or not. Correct. So it's perfectly possible to have a shared and a non-shared reference to the same object, which is potentially dangerous. A DIP* has been accepted that will limit your ability to do dangerous things in this case, but it is not yet implemented. * https://github.com/dlang/DIPs/blob/master/DIPs/accepted/DIP1024.md -- Simen |
June 16, 2020 Re: what exactly does cast(shared) and cast away shared do? | ||||
---|---|---|---|---|
| ||||
Posted in reply to Simen Kjærås | On Tuesday, 16 June 2020 at 08:07:41 UTC, Simen Kjærås wrote: > [..] A DIP* has been accepted that will limit your ability to do dangerous things in this case, but it is not yet implemented. > > > * https://github.com/dlang/DIPs/blob/master/DIPs/accepted/DIP1024.md > > -- > Simen It has been implemented (modulo bugs [1][2]) as of: https://github.com/dlang/dmd/pull/10209 https://github.com/dlang/dmd/pull/11239 [1]: https://issues.dlang.org/show_bug.cgi?id=20195 [2]: https://issues.dlang.org/show_bug.cgi?id=20908 |
June 16, 2020 Re: what exactly does cast(shared) and cast away shared do? | ||||
---|---|---|---|---|
| ||||
Posted in reply to Petar Kirov [ZombineDev] | On Tuesday, 16 June 2020 at 08:32:47 UTC, Petar Kirov [ZombineDev] wrote:
>
> It has been implemented (modulo bugs [1][2]) as of:
>
> https://github.com/dlang/dmd/pull/10209
> https://github.com/dlang/dmd/pull/11239
>
> [1]: https://issues.dlang.org/show_bug.cgi?id=20195
> [2]: https://issues.dlang.org/show_bug.cgi?id=20908
Probably *mostly* implemented is a more accurate way to put it.
|
June 16, 2020 Re: what exactly does cast(shared) and cast away shared do? | ||||
---|---|---|---|---|
| ||||
Posted in reply to Petar Kirov [ZombineDev] | On Tuesday, 16 June 2020 at 08:32:47 UTC, Petar Kirov [ZombineDev] wrote:
> On Tuesday, 16 June 2020 at 08:07:41 UTC, Simen Kjærås wrote:
>> [..] A DIP* has been accepted that will limit your ability to do dangerous things in this case, but it is not yet implemented.
>>
>>
>> * https://github.com/dlang/DIPs/blob/master/DIPs/accepted/DIP1024.md
>>
>> --
>> Simen
>
> It has been implemented (modulo bugs [1][2]) as of:
>
> https://github.com/dlang/dmd/pull/10209
> https://github.com/dlang/dmd/pull/11239
>
> [1]: https://issues.dlang.org/show_bug.cgi?id=20195
> [2]: https://issues.dlang.org/show_bug.cgi?id=20908
By Golly, you're right! I'd forgotten this had actually happened. Thanks! :)
--
Simen
|
June 16, 2020 Re: what exactly does cast(shared) and cast away shared do? | ||||
---|---|---|---|---|
| ||||
Posted in reply to Simen Kjærås | On Tuesday, 16 June 2020 at 08:45:24 UTC, Simen Kjærås wrote: > > By Golly, you're right! I'd forgotten this had actually happened. Thanks! :) > Hehe, you're welcome :) If only all the people on newsgroup were aware of all the amazing developments happening everywhere in D, I think the atmosphere would be much more positive in the General group :) I have been thinking that this would be a cool idea for a YouTube channel. Dart/Flutter have an excellent channel: https://www.youtube.com/channel/UCwXdFgeE9KYzlDdR7TG9cMw Though it 's geared mainly towards to the users of the language and the framework. I have been watching some of their GitHub repos of the past 1 year and I can say that the D development activity is definitely much more interesting :) |
Copyright © 1999-2021 by the D Language Foundation