4

Is the following well defined?

#include <iostream>
#include <string.h>

using namespace std;

struct Const {
    const int i; 
    Const (int i) : i(i) {}
    int get0() { return 0; } // best accessor ever!
};

int main() {
    Const *q,*p = new Const(1);
    new (p) Const(2);
    memcpy (&q, &p, sizeof p);
    cout << q->i;
    return 0;
}

Note that after construction of second Const, p doesn't semantically (intentionally?) points to new object, and the first is gone, so it is usable "as a void*". But the second object is constructed at the exact same address, so the bit pattern of p represents the address of the new object.

COMMENT

new (p) Const(2) erase the old object stored at p, so the pointer is not valid anymore, except as a pointer to storage (void*).

I want to recover the value of p as a Const*.

COMMENT 2

After either p->~Const() or memset (p, 0, sizeof *p) it is clear that p does not point to a valid object, so p can only be used as pointer to storage (void* or char*), for example to reconstruct another object. At that point p->get0() is not allowed.

Here the demolition of the old object is done by the constructor of the new one, but I don't think that makes a difference.

My intuition is that: In any case, the old object is gone, and p points to the old object, not the new one.

I am looking for a confirmation or refutation based on the standard.

SEE ALSO

I have asked essentially the same question about pointers, in C and C++ :

Please read these discussions before answering "this is ridiculous".

Community
  • 1
  • 1
curiousguy
  • 8,038
  • 2
  • 40
  • 58
  • 3
    The `memcpy` should be equivalent to `q = p` simple pointer assignment, shouldn't it? – Daniel Jour Aug 17 '15 at 05:36
  • @DanielJour I don't think so. `p` is not a valid pointer to an object of type `Const` anymore. – curiousguy Aug 17 '15 at 05:37
  • Why isn't it? It's pointing to a correctly aligned memory area in which an object of the right type has been constructed. (Btw, you should probably add some delete operation) – Daniel Jour Aug 17 '15 at 05:40
  • @DanielJour The object it was pointing to is dead, erased. The dtor is trivial; say there is a non-trivial dtor and I call it, if you like. (I don't think it makes a difference.) – curiousguy Aug 17 '15 at 05:45
  • @DanielJour Assignment copies pointer value, I want to copy its bit pattern only. – curiousguy Aug 17 '15 at 05:53
  • 1
    "Assignment copies pointer value, I want to copy its bit pattern only." The pointer value *is* the value of the bits that you are copying with memcpy. – Chris Beck Aug 17 '15 at 06:02
  • 1
    "A pointer depends on the state of the program." A pointer like `p` is a simple value type much like an integer. The value `*p` depends on the state of the program, but the value `p` is self-contained. – Chris Beck Aug 17 '15 at 06:04
  • 2
    [N4430](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4430.html) addresses a similar concern. – Kerrek SB Aug 17 '15 at 06:06
  • @ChrisBeck "_pointer like p is a simple value type much like an integer_" Obviously not. An integer is not invalid, ever. – curiousguy Aug 17 '15 at 06:08
  • 1
    @ChrisBeck: No. For example the fragment `void *p=malloc(1); free(p); printf("%p", p);` is UB because you cannot even print the value of `p` after it has been passed to `free`. – 6502 Aug 17 '15 at 06:08
  • 1
    @curiousguy I think you have a good question here. But I think you're going to have to spell it out a bit because I think most people aren't getting it. – juanchopanza Aug 17 '15 at 06:08
  • 2
    @curiousguy One could argue that an uninitialized `int` is invalid, in the sense that it is UB to read from it. But once initialized, it remains valid. – juanchopanza Aug 17 '15 at 06:08
  • @juanchopanza An `int` object can be invalid, but an `int` value cannot, AFAIK. – curiousguy Aug 17 '15 at 06:09
  • curiousguy: iiuc the only part about your example that undefined is, a conforming implementation is permitted to change the value of p arbitrarily when you call free. if you use `memcpy` instead of `q=p` it doesn't change anything, you are still copying an unspecified value either way, so this whole `memcpy` thing is a misdirection – Chris Beck Aug 17 '15 at 06:14
  • @ChrisBeck Not sure what you mean by "a conforming implementation is permitted to change the value of p when you call free". Anyway I could keep backup copies of p if it was the case. – curiousguy Aug 17 '15 at 06:16
  • curiousguy: having looked at the standard esp. section 3.8 I see this is a good question that I don't know much about, sorry for giving you a hard time. I do think the memcpy thing looks strange though. – Chris Beck Aug 17 '15 at 06:47
  • Actually when I look at the stuff in 3.8.5, the code examples following that, and especially what is said in 3.8.5.2 I wonder if it's UB when you access q->i in your example. I'm not sure though. – Chris Beck Aug 17 '15 at 06:49
  • 1
    *"In any case, the old object is gone, and p points to the old object, not the new one."* - you make a lot of assertions that are blatantly wrong, including this one... would be better to *ask questions* so the disinformation isn't misinterpreted as reliable background info by other readers. – Tony Delroy Aug 17 '15 at 06:51
  • @TonyD Please prove me wrong. – curiousguy Aug 17 '15 at 06:53
  • @curiousguy: what do you think my answer does? You're just not listening. The placement-new ends the old object's lifetime and starts a new object at the same memory address; the pointer `p` remains valid and is left pointing at the new object. If you slow down to think about it, that should be intuitively obvious, and if it's not I can only echo Manu343726's advice. – Tony Delroy Aug 17 '15 at 06:55
  • @TonyD I have changed the question to indicate that this is my personal intuition. – curiousguy Aug 17 '15 at 06:55
  • I don't know if it's appropriate to ask off-topic questions but I now have a lot of questions about how exactly this works. TonyD when you say placement-new ends the old lifetime, does that mean that the original object gets its destructor called, or is that skipped? Or is it undefined what happens if the object has a dtor. (Going to do some experiments / reading now) – Chris Beck Aug 17 '15 at 07:01
  • @ChrisBeck If you don't call the destructor, it won't be called. – curiousguy Aug 17 '15 at 07:16
  • 2
    @ChrisBeck: the original object's destructor is not called... that does not have undefined behaviour as long as the rest of the program doesn't depend on the side-effects of the destructor. See 3.8/1 *"The lifetime of an object of type T ends when: ... the storage which the object occupies is reused or released"*, and 3.8/4's *"any program that depends on the side effects produced by the destructor has undefined behavior"*. – Tony Delroy Aug 17 '15 at 07:19
  • @TonyD "any program that depends on the side effects produced by the destructor has undefined behavior" maybe one of the less intelligible sentence of the standard... – curiousguy Aug 17 '15 at 11:37
  • 1
    @curiousguy: overkill too. I think the provision for reusing the memory is to legitimise effectively discarding old memory pool content - saving time to iterate over and invoke destructors when the objects are of no further relevance. There must be some good way to word a requirement to meet the natural implementation consequences - that the undestructed overwritten objects are simply irrelevant to the rest of the execution. – Tony Delroy Aug 17 '15 at 15:55
  • @juanchopanza "initialized" I am not sure what this word means. – curiousguy Aug 18 '15 at 20:18

2 Answers2

5

(making community-wiki as incorporating dyp's comment re 3.8/7 is very significant; while my earlier analysis was correct I would have said much the same things about code that was broken, having overlooked 3.8/7 myself)

Const *q,*p = new Const(1);
new (p) Const(2);

The new(p) Const(2); line overwrites the object that had been constructed with Const(1).

memcpy (&q, &p, sizeof p);

This is equivalent to q = p;.

cout << q->i;

This accesses the q->i member, which will be 2.

The somewhat noteworthy things are:

  • std::memcpy is an ugly way to assign p to q... it is legal though under 3.9/3:

For any trivially copyable type T, if two pointers to T point to distinct T objects obj1 and obj2, where neither obj1 nor obj2 is a base-class subobject, if the underlying bytes (1.7) making up obj1 are copied into obj2, obj2 shall subsequently hold the same value as obj1. [ Example:

T* t1p;
T* t2p;
// provided that t2p points to an initialized object ...
std::memcpy(t1p, t2p, sizeof(T));
// at this point, every subobject of trivially copyable type in *t1p contains
// the same value as the corresponding subobject in *t2p
  • The overwriting of the old Const(1) object with Const(2) is allowed as long as the program doesn't depend on side effects of the former's destructor, which it doesn't.

  • (as dyp noted in comments below) ongoing access to the Const(2) object using p is illegal under 3.8/7's third point:

pointer that pointed to the original object [...] can be used to manipulate the new object, if...

  • the type of the original object is not const-qualified, and, if a class type, does not contain any non-static data member whose type is const-qualified or a reference type ...
  • using q - rather than p - to access i is presumably necessary to avoid compiler optimisations based on presumed knowledge of i.

As for your commentary:

Note that after construction of second Const, p doesn't semantically (intentionally?) points to new object, and the first is gone, so it is usable "as a void*".

Given you placement-new an object at the address contained in p, p most certainly does point to the newly created object, and very intentionally, but it can't be used to manipulate that object under 3.8/7 as above.

Given you seem to have a notion of "semantically pointing" that's not defined in C++ the truth of that part of the statement's in your own mind.

'after construction of second Const, p...is usable "as a void*' makes no sense... it's not more usable as anything than it was beforehand.

But the second object is constructed at the exact same address, so the bit pattern of p represents the address of the new object.

Of course, but your comments show you think "bit pattern" is somehow distinct from the value of the pointer as applies to assignment with =, which is not true.

new (p) Const(2) erase the old object stored at p, so the pointer is not valid anymore, except as a pointer to storage (void*).

"erase" is a strange term for it... overwrites would be more meaningful. As dyp noted and explained above, 3.8/7 says you shouldn't "manipulate" the object p points to after the placement new, but the value and type of the pointer are unaffected by the placmeent new. Much as you can call f(void*) with a pointer to any type, the placement-new doesn't need to know or care about the type of the p expression.

After either p->~Const() or memset (p, 0, sizeof *p) it is clear that p does not point to a valid object, so p can only be used as pointer to storage (void* or char*), for example to reconstruct another object. At that point p->get0() is not allowed.

Most of that's true, if by "p can only be used" you mean the value of p at that time rather than the pointer itself (which can be of course also be assigned to). And you're trying to be a little too clever with the void* / char* thing - p remains a Const*, even if it's only used by placement new which doesn't care about the pointee type.

"I want to recover the value of p as a Const*."

The value of p was not changed after it was first initialised. placement-new uses the value - it does not modify it. There's nothing to recover as nothing was lost. That said, dyp's highlighted the need not to use p to manipulate the object, so while the value wasn't lost it's not directly usable as wanted either.

Community
  • 1
  • 1
Tony Delroy
  • 102,968
  • 15
  • 177
  • 252
  • A bit pattern of a pointer is never invalid. So a bit pattern is not the same as a value. You can copy the bit pattern of an invalidated pointer. – curiousguy Aug 17 '15 at 06:13
  • 1
    @curiousguy: `p` was properly initialised so it has a value that can be safely accessed and copied using `=`. If `p` were uninitialised or deliberately corrupted, then the distinction becomes relevant and it's possible that `memcpy` would be safe while `=` wouldn't. And BTW - you can copy values of invalidated pointers... the Standard deems a pointer that's not to a currently valid object to have been invalidated, it's different from a bitpattern that doesn't even reference a legitimate address. – Tony Delroy Aug 17 '15 at 06:18
  • 2
    The Bit pattern (*object representation*) contains the value (*value representation*) (§3.9/4) for trivially copyable types, which a pointer is. – Daniel Jour Aug 17 '15 at 06:20
  • So when you destroy an object, the pointers to this object are still valid? – curiousguy Aug 17 '15 at 06:28
  • Also, are you saying that I can legally modify a `const int` variable? – curiousguy Aug 17 '15 at 06:32
  • @curiousguy: the pointer `p` is not "to" `Const(1)` in some mystical sense - it's to the memory where `Const(1) was constructed, which is the same memory where `Const(2)` is later constructed. The pointer `p` remains usable throughout. – Tony Delroy Aug 17 '15 at 06:37
  • 2
    @curiousguy Whether pointers to an object are still "valid" after the object is destroyed depends on exactly what you mean by "valid". They would be invalid to dereference to try to access the original object, but they could become valid by the construction of a new object at the same place. Modifying a `const int` variable is not what's happening here. You're replacing the variable with another one, and that's certainly legal. (For example, `a=b;` can do it if, say `a::some_const_variable` has a different value from `b::some_const_variable`.) – David Schwartz Aug 17 '15 at 06:37
  • @DavidSchwartz Then I can also change the dynamic type of an object. :D – curiousguy Aug 17 '15 at 06:44
  • @TonyD If pointers are not mystical then out of array bound access is allowed, see http://stackoverflow.com/questions/32043795/dereferencing-an-out-of-bound-pointer-that-contains-the-address-of-an-object-ar – curiousguy Aug 17 '15 at 07:15
  • If I `delete` a pointer, call `new`, and by chance `new` gives me the same value, can I use the deleted pointer? – curiousguy Aug 17 '15 at 07:19
  • Regarding the "replacing the variable with another one" that David Schwartz mention mentioned, you can see that in action as part of an answer I wrote not long ago: http://stackoverflow.com/a/31939212/1116364 There's a difference between conceptual constness and not being able to modify a memory location. – Daniel Jour Aug 17 '15 at 07:27
  • 2
    @DavidSchwartz and cc Tony D: The standard explicitly states under which conditions you may use a pointer to an object whose storage has been reused to create another object. In the OP's case, the original object contains a `const` data member, and hence [basic.life]p7.3 says the pointer **does not** point ("refer") to the new object after the placement-new. – dyp Aug 17 '15 at 07:41
  • @dyp: the code in the question copies `p` to `q`, so it's not *"manipulat[ing] the new object"* using the old pointer `p`, but accessing it using the newly assigned `q`, with defined behaviour. That that's significant is very interesting indeed. I'll update my answer to highlight this - thanks. – Tony Delroy Aug 17 '15 at 08:14
  • I'm not entirely sure if overwriting the memory location (where object = region of memory + X) doesn't count as *manipulation*, but IMO the semantics of lifetime + `memcpy` are underspecified anyway. However, the pointer doesn't even "automatically refer to the new object" according to that paragraph. – dyp Aug 17 '15 at 09:25
  • @dyp: English is so easily ambiguous - to me *"a pointer that pointed to the original object...will automatically refer to the new object and, once the lifetime of the new object has started, can be used to manipulate the new object, if"*... makes it sounds like the "automatically refer" bit is unconditional but the ability to manipulate is conditional. Still, if it's this easy for people to interpret differently, compiler writers' takes may differ too and it'd be wisest to - at the very least - make the member non-`const` (better yet, to stop hacking up crappy code). – Tony Delroy Aug 17 '15 at 10:18
  • And the overwriting happens to the old object - it's manipulation of the new object that's potentially problematic, as that's through the pointer value whose trustworthiness we're discussing.... – Tony Delroy Aug 17 '15 at 10:20
  • @TonyD The bullet-point requirements also cover cases where you have a pointer to a subobject, destroy the complete object of which that subobject was part of, and then create an entirely new complete object without that subobject (and different memory layout). E.g. `struct foo { int x; short y; }; struct bar { short x; float y; }; foo f; auto* px = &f.x; new(&f) bar;` – dyp Aug 17 '15 at 10:53
  • @dyp: yes - another reasonable exclusion - any particular reason you mention that in the context of this question, or just find it interesting...? – Tony Delroy Aug 17 '15 at 10:56
  • Sorry, that was a bad example. `struct foo { short x; short y; }; foo f; auto* p = &f.y; auto* q = new((void*)&f) int(42);`, then `p` won't point to any object, especially not to `*q` (which is the new object). I think the bullet points intend to say that, but they only say it if the *automatically refer* is indeed conditional. – dyp Aug 17 '15 at 11:15
  • @dyp: *"The bullet-point requirements also cover cases where you have a pointer to a subobject"* - actually, I can't spot that in n3797 or n3690... in those, 3.8/7 starts "if...a new object is created at the storage location which the original object occupied"* - in your code above you're creating a new object at `&f` so the original object in question is `foo`, and continues *"a pointer that pointed to the original object"* - so we're talking about pointers to `&foo`; I'd hazard that excludes `p`, so the rest of the paragraph doesn't apply (neither "refer to" nor "manipulate"). – Tony Delroy Aug 17 '15 at 12:06
  • @dyp `p` still refers to some (zombie) `short` object that's laying there without doing anything. – curiousguy Aug 17 '15 at 12:32
  • "_semantically pointing_" just means that a pointer can be dereferenced and you get the object it is semantically pointing to – curiousguy Aug 18 '15 at 20:21
  • 2
    @curiousguy: if you want to use terminology used in the C++ Standard or general Computing Science community that's all good, and the discussion will have a meaning other readers can easily follow. But I'm just not interested in discussing C++ using terminology you've invented - it means others can't follow you without jumping around these comments, posts and answers and re-reading everything as they stumble across retrospective definitions of your terms. – Tony Delroy Aug 19 '15 at 03:59
  • @TonyD So tell me what the CS and std C++ terminology is. – curiousguy Aug 19 '15 at 14:39
  • 2
    @curiousguy I'm not commenting on whether there's a term that matches your exact notion, just that throwing terms around as if they will mean the same to others creates more confusion. If you can't find one, and mean a pointer that can be dereferenced such that you access the object whose address it holds, say that. – Tony Delroy Aug 19 '15 at 15:14
  • @TonyDelroy I think the std C++ "throws around" terms by saying that the value of a ptr is fully determined by its bit pattern and they trying to say that some pointers are legal in some context but others are not, when they have the same value. It's a notion of "value" where the value of a variable doesn't determine the valid operations. – curiousguy Jun 07 '18 at 03:21
  • TonyDelroy: "_in some mystical sense_" "_if you want to use terminology used in the C++ Standard or general Computing Science community that's all good_" funny – curiousguy Dec 18 '18 at 22:58
4

This is only intended as an addendum to @Tony D's answer, regarding

new (p) Const(2) erase the old object stored at p

I think you need to differentiate between an object and the conceptual idea of an "instance".

[...] An object is a region of storage.[...]

[N4431 §1.8/1]

So the pointer p points to a region of storage, which contains the bit pattern of some "instance" before the placement new and some different bit pattern of a different, but well constructed "instance" of the correct (same) type.

So at the location pointed to by p there's a valid object, and when assigning q from it q points to it. Though as noted in the other answer, accessing it via p isn't permited.

Daniel Jour
  • 15,896
  • 2
  • 36
  • 63