Thread overview | |||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
|
January 05, 2018 Consequences of casting away immutable from pointers | ||||
---|---|---|---|---|
| ||||
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 } |
January 04, 2018 Re: Consequences of casting away immutable from pointers | ||||
---|---|---|---|---|
| ||||
Posted in reply to jmh530 | 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 |
January 05, 2018 Re: Consequences of casting away immutable from pointers | ||||
---|---|---|---|---|
| ||||
Posted in reply to Steven Schveighoffer | 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.
|
January 04, 2018 Re: Consequences of casting away immutable from pointers | ||||
---|---|---|---|---|
| ||||
Posted in reply to Steven Schveighoffer | On Thursday, January 04, 2018 23:10:54 Steven Schveighoffer via Digitalmars- d-learn wrote:
> 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.
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 Davis
|
January 04, 2018 Re: Consequences of casting away immutable from pointers | ||||
---|---|---|---|---|
| ||||
Posted in reply to Adam D. Ruppe | 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:
> > 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.
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 Davis
|
January 05, 2018 Re: Consequences of casting away immutable from pointers | ||||
---|---|---|---|---|
| ||||
Posted in reply to Steven Schveighoffer | 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.
>
> -Steve
I should have seen that. Thanks. That makes perfect sense.
|
January 05, 2018 Re: Consequences of casting away immutable from pointers | ||||
---|---|---|---|---|
| ||||
Posted in reply to Steven Schveighoffer | 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.
>
> -Steve
I 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!)
|
January 05, 2018 Re: Consequences of casting away immutable from pointers | ||||
---|---|---|---|---|
| ||||
Posted in reply to jmh530 | 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: > > > > 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 > > I 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!) 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 -- Береги платье снову, а здоровье смолоду. |
January 05, 2018 Re: Consequences of casting away immutable from pointers | ||||
---|---|---|---|---|
| ||||
Posted in reply to H. S. Teoh | 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. :-P
>
And 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.
|
Copyright © 1999-2021 by the D Language Foundation