www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - structs holding on to reference data by pointer

reply "Daniel Davidson" <nospam spam.com> writes:
The following seems to work, but feels like luck. When foo 
returns rc should be taken off the stack. If I recall, in C++ 
something like this would crash, but why not here?

import std.stdio;
struct RC {
   this(this) { data = data.dup; }
   int[] data;
}
struct T {
   const(RC) *rc;
   void goo() {
     writeln("Data is ", rc.data);
   }
}

T foo() {
   RC rc = { [1,2,3] };
   return T(&rc);
}

void main() {
   T t = foo();
   t.goo();
}

I'm trying to get around issues with

struct T {
    const(S) s;
}

by avoiding the postblit and having const(S) *s. I just want to 
know that it is always safe and if it is how?

Thanks
Dan
Oct 31 2013
next sibling parent reply "bearophile" <bearophileHUGS lycos.com> writes:
Daniel Davidson:

 import std.stdio;
 struct RC {
   this(this) { data = data.dup; }
   int[] data;
 }
 struct T {
   const(RC) *rc;
   void goo() {
     writeln("Data is ", rc.data);
   }
 }

 T foo() {
   RC rc = { [1,2,3] };
   return T(&rc);
 }

 void main() {
   T t = foo();
   t.goo();
 }
That's wrong code, you are escaping a reference to memory (of rc variable) allocated in the stack frame of foo(). The D compiler is not smart enough to recognize the bug. There are optimizations that patch and avoid this bug (like inlining, or allocating rc inside the stack frame of the main) but you can't rely on them. Bye, bearophile
Oct 31 2013
parent reply "Daniel Davidson" <nospam spam.com> writes:
On Thursday, 31 October 2013 at 16:16:36 UTC, bearophile wrote:
 That's wrong code, you are escaping a reference to memory (of 
 rc variable) allocated in the stack frame of foo(). The D 
 compiler is not smart enough to recognize the bug. There are 
 optimizations that patch and avoid this bug (like inlining, or 
 allocating rc inside the stack frame of the main) but you can't 
 rely on them.
Ahh ok. Because of issues with const members (copying them when they are composed in other classes does not work well) I'm trying to get around the copy by storing reference data as const(T) *. This is why I'm asking. So here are some follow ups: - As I see it there is grave risk. Is the risk introduced by the fact that I am storing a member as const(T)*? - I assume that if I had created the RC instance on the heap there would be no problems. Is there a way in general to ensure that a const(T)* is referring to something on the heap as opposed to the stack (ideally at compile time). - What is the root problem - having a const(T)* member which then requires code to take address, or taking address? My first thought was, just never take address of local static variable - which is generally good advice. But once you are in a function that takes ref const(T) you can take the address of that as well - see modification below. This suffers the same problem but in a less obvious way. Any suggestions welcome. Thanks Dan import opmix.mix; import plus.tvm.rate_curve; import std.stdio; struct RC { this(this) { data = data.dup; } int[] data; } struct T { const(RC) *rc; void goo() { writeln("Data is ", rc.data); } } T goo(ref RC rc) { return T(&rc); } T foo() { RC rc = { [1,2,3] }; writeln("Address is ", &rc); return goo(rc); } void main() { T t = foo(); t.goo(); writeln("Address is ", t.rc); }
Oct 31 2013
parent reply "Maxim Fomin" <maxim maxim-fomin.ru> writes:
On Thursday, 31 October 2013 at 17:34:21 UTC, Daniel Davidson 
wrote:
 On Thursday, 31 October 2013 at 16:16:36 UTC, bearophile wrote:
 That's wrong code, you are escaping a reference to memory (of 
 rc variable) allocated in the stack frame of foo(). The D 
 compiler is not smart enough to recognize the bug. There are 
 optimizations that patch and avoid this bug (like inlining, or 
 allocating rc inside the stack frame of the main) but you 
 can't rely on them.
Ahh ok. Because of issues with const members (copying them when they are composed in other classes does not work well) I'm trying to get around the copy by storing reference data as const(T) *. This is why I'm asking. So here are some follow ups: - As I see it there is grave risk. Is the risk introduced by the fact that I am storing a member as const(T)*?
Risk is that pointer to local variable is escaped, not because it is stored in const qualified pbject.
 - I assume that if I had created the RC instance on the heap 
 there would be no problems. Is there a way in general to ensure 
 that a const(T)* is referring to something on the heap as 
 opposed to the stack (ideally at compile time).
Yes, you can explicitly allocate on heap. You can check whether data is on heap, stack, tls, or just global object by inspecting pointer at runtime. Ideally there would be such function in druntime. It is impossible to do this in CT (except if comiler support flow analysis and can prove in some scenarious that data is on stack or not, but due to separate compilation it is impossible to do in general case) (and probably shouldn't).
 - What is the root problem - having a const(T)* member which 
 then requires code to take address, or taking address? My first 
 thought was, just never take address of local static variable - 
 which is generally good advice. But once you are in a function 
 that takes ref const(T) you can take the address of that as 
 well - see modification below. This suffers the same problem 
 but in a less obvious way.

 Any suggestions welcome.

 Thanks
 Dan

 import opmix.mix;
 import plus.tvm.rate_curve;
 import std.stdio;

 struct RC {
   this(this) { data = data.dup; }
   int[] data;
 }

 struct T {
   const(RC) *rc;
   void goo() {
     writeln("Data is ", rc.data);
   }
 }

 T goo(ref RC rc) {
   return T(&rc);
 }

 T foo() {
   RC rc = { [1,2,3] };
   writeln("Address is ", &rc);
   return goo(rc);
 }

 void main() {
   T t = foo();
   t.goo();
   writeln("Address is ", t.rc);
 }
Yes, this is known issue. There are lots of other tricks to break the language.
Oct 31 2013
parent reply "bearophile" <bearophileHUGS lycos.com> writes:
Maxim Fomin:

 You can check whether data is on heap, stack, tls, or just 
 global object by inspecting pointer at runtime. Ideally there 
 would be such function in druntime.
This seems like a nice enhancement request for Bugzilla :-) Are you going to open it? It seems useful for me too.
 It is impossible to do this in CT (except if comiler support 
 flow analysis and can prove in some scenarious that data is on 
 stack or not, but due to separate compilation it is impossible 
 to do in general case) (and probably shouldn't).
You can do it even with separate compilation (at compile time) if your language has different kinds of pointers as Rust. Perhaps in D this can be done using Phobos-defined smart pointers. Bye, bearophile
Oct 31 2013
parent "Maxim Fomin" <maxim maxim-fomin.ru> writes:
On Thursday, 31 October 2013 at 18:51:46 UTC, bearophile wrote:
 Maxim Fomin:

 You can check whether data is on heap, stack, tls, or just 
 global object by inspecting pointer at runtime. Ideally there 
 would be such function in druntime.
This seems like a nice enhancement request for Bugzilla :-) Are you going to open it? It seems useful for me too.
You are expert in making enhacement request :)
 It is impossible to do this in CT (except if comiler support 
 flow analysis and can prove in some scenarious that data is on 
 stack or not, but due to separate compilation it is impossible 
 to do in general case) (and probably shouldn't).
You can do it even with separate compilation (at compile time) if your language has different kinds of pointers as Rust. Perhaps in D this can be done using Phobos-defined smart pointers. Bye, bearophile
But I don't believe that D being system laguage will diverge from traidtional C/C++ pointer model. Right, in runtime smart pointers can probably do some job by guessing allocation kind looking at pointer.
Oct 31 2013
prev sibling parent "Maxim Fomin" <maxim maxim-fomin.ru> writes:
On Thursday, 31 October 2013 at 15:11:48 UTC, Daniel Davidson 
wrote:
 The following seems to work, but feels like luck. When foo 
 returns rc should be taken off the stack. If I recall, in C++ 
 something like this would crash, but why not here?
This ineed runs by luck. In C++ it would not necessarily crash, but it is UB like in D. You can workaround issue by: import std.stdio; struct S { int[] data; const this(this) { S *s = cast(S*)&this; s.data = s.data.dup; } } struct SS { const S s; } void main() { S s1, s2; s1.data = s2.data = [0,1]; s1 = s2; s2.data[0] = 1; writeln(s1.data); } but this breaks constness and penalty is that function with casts cannot be run in safe code. (However in this case there is no logical mutation because 's' keeps its value. Probably self modifying const postblits should be allowed with restriction that it is programmer duty to ensure that const object is duplicated, but not mutated - at least judging by postblit definition it is reasonable assumption with some degree of confidence.)
Oct 31 2013