3

The answer to this question refers to this page which says that, in order for delete this; to behave correctly:

You must be absolutely 100% positively sure that no one even touches the this pointer itself after the delete this line. In other words, you must not examine it, compare it with another pointer, compare it with NULL, print it, cast it, do anything with it.

I don't understand why touching the pointer itself causes problems.
Could someone please explain it to me?

Community
  • 1
  • 1
user541686
  • 205,094
  • 128
  • 528
  • 886
  • is `printf("%p", some_pointer);` even defined when `some_pointer` has type other than `void*` and there is no cast? – Ben Voigt Jul 12 '13 at 17:16
  • @BenVoigt: Hmm, if that bothers you, just pretend that I've written `this == this;` instead. That's not what the question is about anyway. – user541686 Jul 12 '13 at 17:16
  • 1
    @user93353: Thanks for finding that! [Steve's answer](http://stackoverflow.com/a/1866693/541686) answers my question! – user541686 Jul 12 '13 at 17:22
  • @Mehrdad - So the verdict is that this isn't `this`-specific, right? – DaoWen Jul 12 '13 at 17:25

2 Answers2

4

It will probably work on most modern hardware, but it is undefined behavior. I believe the rationale (I read this somewhere online) is that some architectures have trapping capabilities in their pointer registers so if you load a pointer into a register and that pointer points to unallocated memory, the architecture might fire a signal.

Adam H. Peterson
  • 4,511
  • 20
  • 28
  • That would be true of _any_ freed pointer then (not just `this`), correct? – DaoWen Jul 12 '13 at 17:06
  • So the fact that we say `this` is irrelevant? As in, if I had said `delete p; printf("%p", p);`, then the same problem would have existed, right? – user541686 Jul 12 '13 at 17:07
  • @DaoWen Yes. But it depends on a lot of things. Intel (and I think the Motorola 68000 as well) have this possibility; on the Intel, however, it will only occur if the OS allocates each block in a separate segment (which none do), and if `malloc` and `operator new` go to the OS for each allocation (none do). – James Kanze Jul 12 '13 at 17:08
  • Wouldn't an address go in an integer register anyway, not a pointer register, unless it was actually being dereferenced? – Ben Voigt Jul 12 '13 at 17:10
  • @BenVoigt: Isn't that an implementation detail? – user541686 Jul 12 '13 at 17:11
  • @Mehrdad, yes, the issue is with the deleted pointer, not whether that pointer is `this`. – Adam H. Peterson Jul 12 '13 at 17:14
  • @JamesKanze - Sorry, I guess I don't have enough systems programming background to make sense of your reply. From what I understood, the x86 doesn't have dedicated "pointer registers," so I don't see how printing the value stored in a register could cause this issue. (I don't understand why allocating blocks in separate segments would be problematic either, for the same reason that general-purpose register values are type-agnostic.) – DaoWen Jul 12 '13 at 17:14
  • @BenVoigt, that's up to the implementation. By making this usage illegal, the architecture is free to choose whether to use pointer registers or integer registers directly. If the implementation can assume that any read of a pointer requires the pointer to be valid, it can choose to always read a pointer into a pointer register, rather than perhaps being forced to load a pointer into a same-sized integer register until it can guarantee the pointer will be dereferenced and then move it into a pointer register. – Adam H. Peterson Jul 12 '13 at 17:18
  • @JamesKanze, I'm not aware of this behavior of X86 pointers from my asm experience (which, admittedly, is fifteen years out of date). Could you elaborate? I know, of course, that preemptively following a pointer that may be deallocated could cause a page fault. (That behavior wouldn't require a separate segment for each allocation, though; you could get (un)lucky and hit the one allocation that was alone in its segment.) – Adam H. Peterson Jul 12 '13 at 17:22
  • A one-past-the-end of an array is always a well-defined pointer according to C/C++, but the dereference isn't. It would require a pathological pointer implementation (that handles array ranges on a HW level) to enforce this. Not *impossible* I guess. – Brett Hale Jul 12 '13 at 17:56
  • @BrettHale, if an architecture has trapping pointers and they trap, for example, at a 4K segment block level, the implementation would simply have to ensure that the end of an allocation is never at the end of a 4K block. This might occasionally require a few wasted words in memory fragmentation, but it would allow the platform to remain standards-compliant. (Note that the hypothetical trapping platform doesn't necessarily trap on the load of *every* invalid pointer.) – Adam H. Peterson Jul 12 '13 at 18:00
  • @AdamH.Peterson: Actually, I found a loophole in your reasoning: What if I do `void *p = this; delete this; printf("%p", p);`? By your reasoning, since `p` doesn't point to a valid location, this should crash any machine that checks for valid pointers, but isn't this 100% legal according to C++? – user541686 Jul 12 '13 at 20:10
  • @Mehrdad: See the footnote in 3.7.4.3: "Some implementations might define that copying an invalid pointer value causes a system-generated runtime fault." – Ben Voigt Jul 12 '13 at 20:15
  • @Mehrdad, no, this isn't legal. A pointer that points to deallocated memory is the same whether or not this particular copy of it was used to delete the memory. Otherwise, You could do `delete this+0; printf("%p", this);`; the expression `this+0` would create a temporary pointer with the same value as `this`, and that would be deleted. The only way I can see you could do something like this legally is to make the type of `p` a `uintptr_t`. – Adam H. Peterson Jul 12 '13 at 20:16
  • Correction: `delete` has higher precedence than `+`, so that would have to be `delete (this+0);` – Adam H. Peterson Jul 12 '13 at 20:24
  • @Mehrdad, it seems to me that you knew this already in your reference to "Steve's answer" in your comment below the question, where he quotes the standard as saying that deallocating a region of storage renders all pointers into that storage invalid and making the use of any of them undefined. – Adam H. Peterson Jul 12 '13 at 20:28
  • @AdamH.Peterson: Actually I had no idea that was the case -- my understanding was that `delete` itself might modify the pointer to make it invalid, but I thought pointers were OK to copy around if they weren't touched. That's kinda scary now that I see it... thanks. – user541686 Jul 12 '13 at 20:32
  • 1
    @AdamH.Peterson An address on an 80x86 has two parts, a segment and an offset. Loading an illegal segment into a segment register will cause the the system to trap. And an implementation is free to use the segment registers when copying a pointer. (It's true that most current systems ignore the segment registers completely, loading them once and then never touching them, to give the impression of linear addressing. But this wasn't always the case, especially with the 80286, where the offset was only 16 bits.) – James Kanze Jul 13 '13 at 17:18
  • @JamesKanze, thanks. I knew that the 16-bit x86 architectures (and probably the early 32-bit flavors?) were segmented, but I never realized that loading an invalid segment would trap. Now that you say it, though, it makes sense. – Adam H. Peterson Jul 13 '13 at 18:13
  • @AdamH.Peterson 32-bit x86 architecture is also segmented. We often ignore the fact, however, because the two most widespread OS's for it load the segment registers at start up, and never change them, only using the offsets. (But I've worked on 32 bit x86 platforms which did use 48 bit addresses, with segments.) – James Kanze Jul 14 '13 at 14:37
2

The this pointer is a little bit special, because calculating it on an object using virtual inheritance requires reading virtual subobject offsets through the object itself. (I would expect that to be done before by the caller before entry to the member function, but I don't know whether 100% of real-world compilers do)

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • If that's the case then `delete this` shouldn't even be legal in those contexts since it's pointing to a "sub-object offset" within the actual allocated block, right? – DaoWen Jul 12 '13 at 17:16
  • @DaoWen: But the argument is evaluated before `delete`... at which time it is still possible to do the computation and find the full object to delete. And surely one is using a virtual destructor in such cases. – Ben Voigt Jul 12 '13 at 17:16
  • 1
    Doesn't `this` **have** to be calculated before the function is entered? It's not like the function somehow "knows" that the pointer refers to a subclass, it expects a pointer to the current class... – user541686 Jul 12 '13 at 17:20
  • @Mehrdad - Yes! I was trying to figure out a good way to phrase that. You nailed it. If you're passing in a pointer to a "sub-object", then the function you're calling has no way to know it's not a normal pointer that can be deleted, so there's no way for it to go back and "find the full object to delete." – DaoWen Jul 12 '13 at 17:21
  • @Mehrdad: Virtual inheritance gets *weird*. It might be useful for a `this` pointer to a class with virtual bases to be implemented as a pointer to a table of subobject pointers stored inside the object... which just went away as a result of `delete this;`. – Ben Voigt Jul 12 '13 at 17:23
  • @BenVoigt: Is that the only scenario that's weird? Is non-virtual inheritance fine? – user541686 Jul 12 '13 at 17:24
  • @DaoWen: Of course the function has a way to find the full object. It's required to, if you write code like `dynamic_cast(this)`. – Ben Voigt Jul 12 '13 at 17:24
  • @BenVoigt: Ah right... but I think you mean `dynamic_cast(this)`? – user541686 Jul 12 '13 at 17:25
  • @BenVoigt - Good point, I need to go back and refresh my memory on how inheritance, virtual function tables, etc are implemented in C++. – DaoWen Jul 12 '13 at 17:27
  • @Mehrdad: Good catch. I thought `static_cast` also returned a pointer to the complete objcet, but it looks like that behavior is part of `dynamic_cast`. Thanks. – Ben Voigt Jul 12 '13 at 17:28
  • Is this even specific to `this`? – user541686 Jul 12 '13 at 17:30
  • @Mehrdad: Well, it also applies to `this->whatever_member`, including cases where the source code doesn't explicitly say `this`. – Ben Voigt Jul 12 '13 at 17:30
  • I don't see how any of this is specific to `this` or how this relates to just reading a pointer value. If your function takes a `Foo*` as a parameter, all the same offset calculations apply as if you took `Foo*` as the implicit `this` parameter. And to do any of the mentioned virtual calculations, you would have to do either a dereference or a cast/conversion, which both go beyond simply reading a pointer value. Am I missing something? – Adam H. Peterson Jul 12 '13 at 17:39
  • @Adam: Yes, I think you're right. Doing anything with any pointer with invalid value and whose static type has virtual bases has some justification for failing in the real-world. – Ben Voigt Jul 12 '13 at 17:42
  • I think in this context, the only way that `this` is special (relative to any other pointer) is in how easy it is to dereference without noticing. To dereference any other pointer, you have to mention the pointer in the text of the program to apply `*` or `->` to it (which is obviously a read). With `this`, you implicitly dereference by mentioning any nonstatic member (in an evaluated context), including a member variable or member function of the current class or of any base. Any such mention constitutes a dereference (and therefore a read), and reading a deallocated pointer is UB. – Adam H. Peterson Jul 12 '13 at 20:09