digitalmars.D.learn - Consequences of casting away immutable from pointers
- jmh530 (22/22) Jan 04 2018 I'm trying to understand the consequences of casting away
- Steven Schveighoffer (10/17) Jan 04 2018 Yes, this is undefined behavior.
- Adam D. Ruppe (13/16) Jan 04 2018 I was curious what dmd did, and the disassembly indeed shows it
- Jonathan M Davis (5/21) Jan 04 2018 Well, it's certainly nice to see some evidence that the compiler really ...
- Jonathan M Davis (9/25) Jan 04 2018 Yeah, casting away either const or immutable is just begging for trouble...
- jmh530 (3/12) Jan 05 2018 I should have seen that. Thanks. That makes perfect sense.
- jmh530 (6/15) Jan 05 2018 I also checked that if you create an instance of a class on the
- H. S. Teoh (17/38) Jan 05 2018 Be careful with that:
- Patrick Schluter (6/21) Jan 05 2018 And these things are nasty. We had one in our C project last
I'm trying to understand the consequences of casting away immutable from a pointer. The code below has some weird things going on like the pointers still point to the correct address but when you dereference them they don't point to the correct value anymore. Should I just assume this is undefined behavior and not bother with it? Or is there a use case for this? void main() { immutable(int) x = 5; auto p_x = &x; int* p_x_alt = cast(int*)p_x; (*p_x_alt)++; //addresses remain unchanged assert(&x == p_x); assert(p_x == p_x_alt); assert(*p_x_alt == 6); assert(*p_x == *p_x_alt); //but p_x and p_x_alt point to same value assert(x != *p_x); //yet that is not the case for x assert(x == 5); //which still is 5 }
Jan 04 2018
On 1/4/18 10:58 PM, jmh530 wrote:I'm trying to understand the consequences of casting away immutable from a pointer. The code below has some weird things going on like the pointers still point to the correct address but when you dereference them they don't point to the correct value anymore. Should I just assume this is undefined behavior and not bother with it? Or is there a use case for this?Yes, this is undefined behavior. https://dlang.org/spec/const3.html#removing_with_cast The compiler assumes x is going to be 5 forever, so instead of loading the value at that address, it just loads 5 into a register (or maybe it just folds x == 5 into true). The compiler would likely be free to assume *p_x == 5 forever also, if it was clever enough. I'd recommend not doing this. -Steve
Jan 04 2018
On Friday, 5 January 2018 at 04:10:54 UTC, Steven Schveighoffer wrote:The compiler assumes x is going to be 5 forever, so instead of loading the value at that address, it just loads 5 into a register (or maybe it just folds x == 5 into true).I was curious what dmd did, and the disassembly indeed shows it just loads 5 into the register and leaves it there - assuming since it is immutable, it will never change through any pointer and thus never reloads it from memory at any time. Interestingly, dmd -O just stubs out the whole function. I guess it assumes all the defined behavior actually accomplishes nothing and it is free to optimize out undefined behavior... thus the function needs no code. Similarly, if the last assert is changed to x != 5, dmd -O doesn't even actually do a comparison (the value 5 never appears in the generated code!), it just outputs the direct call to assertion failure.
Jan 04 2018
On Friday, January 05, 2018 04:16:48 Adam D. Ruppe via Digitalmars-d-learn wrote:On Friday, 5 January 2018 at 04:10:54 UTC, Steven Schveighoffer wrote:Well, it's certainly nice to see some evidence that the compiler really is taking advantage of the guarantees that immutable is supposed to provide. - Jonathan M DavisThe compiler assumes x is going to be 5 forever, so instead of loading the value at that address, it just loads 5 into a register (or maybe it just folds x == 5 into true).I was curious what dmd did, and the disassembly indeed shows it just loads 5 into the register and leaves it there - assuming since it is immutable, it will never change through any pointer and thus never reloads it from memory at any time. Interestingly, dmd -O just stubs out the whole function. I guess it assumes all the defined behavior actually accomplishes nothing and it is free to optimize out undefined behavior... thus the function needs no code. Similarly, if the last assert is changed to x != 5, dmd -O doesn't even actually do a comparison (the value 5 never appears in the generated code!), it just outputs the direct call to assertion failure.
Jan 04 2018
On Thursday, January 04, 2018 23:10:54 Steven Schveighoffer via Digitalmars- d-learn wrote:On 1/4/18 10:58 PM, jmh530 wrote:Yeah, casting away either const or immutable is just begging for trouble, though it's likely to be worse with immutable, since there are more optimizations that the compiler can do based on immutable. D's const and immutable are definitely not the same as C++'s const and treating either of them like they have backdoors is just going to cause bugs. If you ever need a backdoor to get around them, then you shouldn't be using them. - Jonathan M DavisI'm trying to understand the consequences of casting away immutable from a pointer. The code below has some weird things going on like the pointers still point to the correct address but when you dereference them they don't point to the correct value anymore. Should I just assume this is undefined behavior and not bother with it? Or is there a use case for this?Yes, this is undefined behavior. https://dlang.org/spec/const3.html#removing_with_cast The compiler assumes x is going to be 5 forever, so instead of loading the value at that address, it just loads 5 into a register (or maybe it just folds x == 5 into true). The compiler would likely be free to assume *p_x == 5 forever also, if it was clever enough. I'd recommend not doing this.
Jan 04 2018
On Friday, 5 January 2018 at 04:10:54 UTC, Steven Schveighoffer wrote:Yes, this is undefined behavior. https://dlang.org/spec/const3.html#removing_with_cast The compiler assumes x is going to be 5 forever, so instead of loading the value at that address, it just loads 5 into a register (or maybe it just folds x == 5 into true). The compiler would likely be free to assume *p_x == 5 forever also, if it was clever enough. I'd recommend not doing this. -SteveI should have seen that. Thanks. That makes perfect sense.
Jan 05 2018
On Friday, 5 January 2018 at 04:10:54 UTC, Steven Schveighoffer wrote:Yes, this is undefined behavior. https://dlang.org/spec/const3.html#removing_with_cast The compiler assumes x is going to be 5 forever, so instead of loading the value at that address, it just loads 5 into a register (or maybe it just folds x == 5 into true). The compiler would likely be free to assume *p_x == 5 forever also, if it was clever enough. I'd recommend not doing this. -SteveI also checked that if you create an instance of a class on the heap with an immutable constructor, then it's no longer in the register. Thus, I can now modify the immutable object from the pointer that I casted away immutable (though not that I would!)
Jan 05 2018
On Fri, Jan 05, 2018 at 05:50:34PM +0000, jmh530 via Digitalmars-d-learn wrote:On Friday, 5 January 2018 at 04:10:54 UTC, Steven Schveighoffer wrote:Be careful with that: class C { int x; } immutable C c = new C(5); auto i = c.x; C y = cast(C) c; y.x = 10; i = c.x; // <-- compiler may assume c.x is still 5 Since c.x is read from an immutable object, the compiler may assume that its value hasn't changed the second time you access it, so it may just elide the second assignment to i completely, thereby introducing a bug into the code. Basically, casting away immutable is UB, and playing with UB is playing with fire. :-P T -- Береги платье снову, а здоровье смолоду.Yes, this is undefined behavior. https://dlang.org/spec/const3.html#removing_with_cast The compiler assumes x is going to be 5 forever, so instead of loading the value at that address, it just loads 5 into a register (or maybe it just folds x == 5 into true). The compiler would likely be free to assume *p_x == 5 forever also, if it was clever enough. I'd recommend not doing this. -SteveI also checked that if you create an instance of a class on the heap with an immutable constructor, then it's no longer in the register. Thus, I can now modify the immutable object from the pointer that I casted away immutable (though not that I would!)
Jan 05 2018
On Friday, 5 January 2018 at 18:13:11 UTC, H. S. Teoh wrote:On Fri, Jan 05, 2018 at 05:50:34PM +0000, jmh530 via Digitalmars-d-learn wrote: Be careful with that: class C { int x; } immutable C c = new C(5); auto i = c.x; C y = cast(C) c; y.x = 10; i = c.x; // <-- compiler may assume c.x is still 5 Since c.x is read from an immutable object, the compiler may assume that its value hasn't changed the second time you access it, so it may just elide the second assignment to i completely, thereby introducing a bug into the code. Basically, casting away immutable is UB, and playing with UB is playing with fire. :-PAnd these things are nasty. We had one in our C project last month that had us tear our hair out. It was in the end a documentation problem of gcc that induced the misunderstanding of the purpose of __attribut__((malloc)) and its effect on aliased pointer.
Jan 05 2018