27

In this paragraph of C++ FAQ usage of delete this construct is discussed. 4 restrictions are listed.

Restrictions 1 to 3 look quite reasonable. But why is restriction 4 there that I "must not examine it, compare it with another pointer, compare it with NULL, print it, cast it, do anything with it"?

I mean this is yet another pointer. Why can't I reinterpret_cast it to an int or call printf() to output its value?

seaotternerd
  • 5,872
  • 2
  • 40
  • 58
sharptooth
  • 159,303
  • 82
  • 478
  • 911

7 Answers7

37

The value of 'this' after calling delete is undefined, and the behaviour of anything you do with it is also undefined. While I would expect most compilers to do something sensible, there's nothing (in the spec) stopping the compiler from deciding that its behaviour in this particular case will be emit code to format your hard-disk. Invoking undefined behaviour is (almost) always a mistake, even when your particular compiler behaves in the way you'd like it to.

You could work around this by taking a copy of the pointer (as an integer) before calling delete.

Andrew Aylett
  • 36,202
  • 4
  • 63
  • 92
  • 4
    Additionally, the way the compiler behaves today might not be the same as the next version does. Undefined really means "not guaranteed, ever". – Lasse V. Karlsen Dec 08 '09 at 12:28
  • +1, definitely the *correct* answer. To extend Lasse's response... _"undefined"_ also implies _likely to be inconsistent_. – D.Shawley Dec 08 '09 at 13:00
  • 3
    Although you could take the integer value of the pointer before deleting it - its a good rule of thumb that if you're circumventing the language's mechanics to tread into "undefined behavior" territory - that you're just asking for trouble, and a better fundamental design to your code should be considered. – Mordachai Dec 08 '09 at 14:00
  • 1
    Emitting opcodes for formatting the hard drive? And don't type Google into Google, not even for fun, it could *break* the internet. – Cecil Has a Name Dec 08 '09 at 21:59
  • It'd be standards compliant, that's all I said :). See also http://www.catb.org/jargon/html/N/nasal-demons.html – Andrew Aylett Dec 08 '09 at 22:30
  • I'm confused. How is the value of `this` undefined? It's the same value it was before, just like any other pointer. – GManNickG Dec 09 '09 at 22:08
  • @GMan: If you look at Steve Jessop's answer, you'll see the part of the spec that applies. It's not that the values *will* magically change, but that the spec doesn't define what will happen when the value is used, so the compiler doesn't have to worry about doing something sensible or consistent. Most of the compilers I've seen would maintain the value, as breaking horribly would be more effort, but you shouldn't rely on it. Some will raise a warning (or error) as part of, say, MISRA C checking. – Andrew Aylett Dec 10 '09 at 10:03
  • 1
    -1 because this *reasoning* is **wrong**. For example, `void *p = this; delete this; p == p;` is also wrong, but it's **not** because the value of `p` changed (it didn't). – user541686 Jul 13 '13 at 07:51
29

The reason that you cannot do anything with a pointer after you delete it (this, or any other pointer), is that the hardware could (and some older machines did) trap trying to load an invalid memory address into a register. Even though it may be fine on all modern hardware, the standard says that the only thing that you can do to a invalid pointer (uninitialized or deleted), is to assign to it (either NULL, or from another valid pointer).

KeithB
  • 15,563
  • 2
  • 37
  • 44
  • but wouldn't that be applicable for all pointers? why only for `this`? – Naveen Dec 08 '09 at 12:11
  • 2
    I'll accept this answer because it explains why exactly hell can break loose, not just cites The Standard. – sharptooth Dec 14 '09 at 15:06
  • and that "old" hardware is the old Intel x86 family. ;) – curiousguy Sep 29 '11 at 02:32
  • While this explains why handling an uninitialised pointer invokes undefined behaviour, I find it highly unplausible for a pointer value (=address) that was perfectly valid before calling `delete`, that the same value starts triggering _hardware_ traps or similar due to the simple fact that the memory location it points to was deallocated. Indeed the pointer value may already be sitting in such a register at the time `delete` is called. – Marc van Leeuwen Jul 13 '14 at 07:56
  • 1
    @MarcvanLeeuwen: It would be possible (and actually not unreasonable) for a compiler on a platform like the 80286 to have objects larger than 32K be represented as a linked-list of segments, each of which had 32K or less of data and included links to the first, next, previous segment of that object, and an indication of its offset into the object. Each pointer would identify a segment and an offset within it; comparing two pointers would require using the data stored at the base of each pointer's segment to find the start of the object. – supercat May 29 '15 at 17:46
  • @MarcvanLeeuwen: I do think there should be a means by which compilers could indicate that they're *not* going to do something like that, and by which code could be declared "strictly conforming subject to XX requirements", but I know of nothing in any standard that would allow such a concept. – supercat May 29 '15 at 17:47
28

Aha!

3.7.3.2/4: "... the deallocation function shall deallocate the storage referenced by the pointer, rendering invalid all pointers referring to any part of the deallocated storage. The effect of using an invalid pointer value (including passing it to a deallocation function) is undefined".

Note that this says "using the value", not "dereferencing the pointer".

That paragraph is not specific to this, it applies to anything that has been deleted.

Steve Jessop
  • 257,525
  • 32
  • 431
  • 672
  • +1: even more direct than axa's answer. Very nice find Steve. – D.Shawley Dec 08 '09 at 13:01
  • so that means the normal practice of `delete p; p = NULL;` invokes undefined behavior? – Naveen Dec 08 '09 at 15:16
  • 2
    @naveen, no. p = NULL is still valid. We are changing the pointer to a new location, not using the value of the delete pointer. If it were otherwise, all pointer variables would only be able to be used once. – deft_code Dec 08 '09 at 15:30
  • 3
    Agree. `p = NULL` is fine, but `p = p - p;` is not – Steve Jessop Dec 08 '09 at 16:17
  • 3
    `p = p - p;` is never valid :) – avakar Dec 08 '09 at 17:38
  • Why not? `p - p` is 0 in some kind of integer type, and a 0 integer is guranteed to convert to a NULL pointer. Or maybe I'm wrong, but hopefully you get the general idea. `p = p;`, if you like, or `p = p ? p : NULL;` – Steve Jessop Dec 08 '09 at 18:33
  • Are comparisons not included in the word "using"? If they are included, then `if (p == NULL)` would only be valid if p is, in fact, `NULL` - a rather strange situation. – Michael Myers Dec 08 '09 at 19:16
  • 2
    `p-p` is "zero the integer" not "zero the null pointer constant". The former won't convert to a null pointer whereas the latter will. – avakar Dec 08 '09 at 23:24
  • That's because after `const int i = 0`, `i` is a constant expression that evaluates to zero and, therefore, converts to a null pointer value. In the latter example, `i` is not a constant expression and an assignment to a pointer is ill-formed (and thus shouldn't compile). You say that gcc didn't diagnose the error -- are you sure you had `p=i` in your source text? You wrote `p=0` in the comment above. – avakar Dec 09 '09 at 08:37
  • In fact I do get a warning (not an error) from gcc for `p = i;`, whether i is const or not, and whether for initialization or assignment. I didn't test that before, I just assumed that since I didn't get a warning for `p - p`, I wouldn't for other assignments from integers. I'm confused now how gcc is doing its diagnosis, but it's not rejecting any of it. – Steve Jessop Dec 09 '09 at 12:54
  • Strange, I get no warning for the former case (save for "unused variable ‘p’") and an error for the latter. I have g++-4.3.2, building with "g++ -ansi -pedantic -Wall". – avakar Dec 09 '09 at 20:17
  • What about STL-style past-the-end pointers for iterator ranges? They are never valid pointers, yet it's totally acceptable to do anything with them except dereference them. – James Brock Jan 23 '13 at 12:32
  • @James: `expr.add` defines what pointers you can form with addition, including one-past-the-end-of-an-array. I don't think that pointers formed by following those rules are "invalid", unless of course the pointer you start from is invalid or the allocation is later freed. So I would say that off-the-end pointers *are* valid, but as you say it's UB to dereference them. Other examples of valid pointers that you can't dereference include null pointers and (for an unrelated reason) `void*`. – Steve Jessop Jan 23 '13 at 12:38
3

because any action you can take with that pointer could trigger logic which is interpreted on the class methods of that object, which could lead to a crash.

Now, some of the actions you point at could be apparently "safe", but it's difficult to say what happens within any method you can call.

From the post: "must not examine it, compare it with another pointer, compare it with NULL, print it, cast it, do anything with it"?

All these actions can trigger operator related functions, which are evaluated with an undefined pointer. Idem for casting.

Now if you perform a reintepret_cast, that's probably a different story, and you could probably get along with it, as reinterpret is just a bit by bit reinterpretation, without involving (as far as I know) any method call.

Stefano Borini
  • 125,999
  • 87
  • 277
  • 404
2

For the same reason you would not delete any other pointer and then try and perform any operations on it.

DanDan
  • 10,037
  • 8
  • 49
  • 68
  • 1
    I would definitely be able to printf() a pointer value after that. But the FAQ says it shouldn'be done on "this". – sharptooth Dec 08 '09 at 11:55
  • 1
    Fair enough, if you really want to :) I suppose the FAQ is just being over-cautious. – DanDan Dec 08 '09 at 11:58
  • 3
    sharptooth, printing the value of a freed pointer triggers undefined behavior, just like printing freed `this`. – avakar Dec 08 '09 at 12:29
1

b/c the address that this refers to now, it undefined, and you don't know what might be there...

Dani
  • 13,366
  • 11
  • 56
  • 99
1

In a multi-threaded program, the moment you delete a pointer, the free space can be allocated by another thread, overwriting the space used by this. Even in a single-thread program, unless you're very careful about what you call before returning, anything you do after delete this could allocate memory and overwrite what used to be pointed to by this.

In a Microsoft Visual C++ executable compiled in Debug mode, deleteing a pointer causes its memory to be immediately overwritten with a 0xCC test pattern (uninitialized variables are also initialized with this pattern), to help in identifying dangling pointer bugs such as this one.

This reminds me of when I fixed a bug in a online-playable game in which a Fire object's constructor deleted the oldest Fire if the total number of Fires had reached a certain number. The deleted Fire was sometimes the parent Fire creating a new Fire — bam, dangling pointer bug! It was only due to luck that this bug interacted with the memory allocation algorithm in a completely predictable way (the deleted Fire was always overwritten with a new Fire in the same way) — otherwise it would have caused a desynchronization between online players. I found this bug when rewriting the way the game did memory allocation. Due to its predictability, when I fixed it, I was also able to implement emulation of its behavior for compatibility with older game clients.

Deadcode
  • 820
  • 9
  • 15
  • 2
    The question isn't about the accessing memory the pointer points to, but rather accessing the pointer itself (e.g. why even just doing a printf("%p\n", this) would be undefined behavior after a doing a 'delete this') – Jeremy Friesner Jun 12 '14 at 05:43