digitalmars.D.learn - structs holding on to reference data by pointer
- Daniel Davidson (30/30) Oct 31 2013 The following seems to work, but feels like luck. When foo
- bearophile (8/27) Oct 31 2013 That's wrong code, you are escaping a reference to memory (of rc
- Daniel Davidson (47/53) Oct 31 2013 Ahh ok. Because of issues with const members (copying them when
- Maxim Fomin (13/68) Oct 31 2013 Risk is that pointer to local variable is escaped, not because it
- bearophile (8/15) Oct 31 2013 This seems like a nice enhancement request for Bugzilla :-) Are
- Maxim Fomin (6/22) Oct 31 2013 But I don't believe that D being system laguage will diverge from
- Maxim Fomin (30/33) Oct 31 2013 This ineed runs by luck. In C++ it would not necessarily crash,
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
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
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
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:Risk is that pointer to local variable is escaped, not because it is stored in const qualified pbject.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).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
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
On Thursday, 31 October 2013 at 18:51:46 UTC, bearophile wrote:Maxim Fomin:You are expert in making enhacement request :)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.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.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
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