4

I found this snippet

void* operator new(size_t nbytes)
{
  if (nbytes == 0)
    nbytes = 1;                    // so all alloc's get a distinct address
  void* ans = malloc(nbytes + 4);  // overallocate by 4 bytes
  *(Pool**)ans = NULL;             // use NULL in the global new
  return (char*)ans + 4;           // don't let users see the Pool*
}

here https://isocpp.org/wiki/faq/dtors

I spent over an hour now trying t understand what *(Pool**)ans = NULL; does. ans is a void pointer, so I would assume it is cast to a Pool pointer and the pool is set to 0. Not he pointer but the pool itself, because of the third * on the left. But Pool has no operator= defined.

pointer** in a declaration is apparently a pointer to a pointer... but in this context this makes no sense to me, as ans is a single pointer.

0

2 Answers 2

7

The only reason to use Pool** here is semantic correctness, since presumably that "hidden" 4-byte header is supposed to be a pointer to a Pool (so ans is a pointer to a pointer to a Pool, and *(Pool **)ans has type Pool *).

You couldn't do *(Pool *)ans = NULL unless you were able to assign a Pool to NULL, and that is probably not the intended effect here anyways. Something like *(int **)ans = NULL or the more ridiculous *(Pool ******)ans = NULL would've had the same end effect but would have been semantically odd if it is ultimately intended to be a pointer to a Pool.

At the end of the day you'll end up with:

 +---+---+---+---+- - -
 | 0 | 0 | 0 | 0 | ... and nbytes more bytes
 +---+---+---+---+- - -

 ^ ans           ^ returned address (ans + 4)

Where those first 4 bytes are intended to be a pointer to a Pool somewhere.

Another way to think about this is to ignore the whole nbytes thing, consider this general pattern:

void * ptr = malloc(sizeof(TYPE));
*(TYPE *)ptr = VALUE;

That should make sense. Now then if TYPE is a Pool * and VALUE is NULL and you fit it into that pattern, you can see how it all makes sense still:

void * ptr = malloc(sizeof(Pool *));
*(Pool **)ptr = NULL;

And then in your case you're still basically doing that although you're allocating some extra bytes at the end, but that's irrelevant to the types here.

As an aside, it's potentially asking for trouble here (and also semantically lazy) to hard-code 4 everywhere instead of sizeof(Pool *).

Sign up to request clarification or add additional context in comments.

Comments

3

No, it is not "cast to a Pool pointer". The cast is a:

(Pool**)

This is not a pointer to a Pool. This is a pointer to a pointer to a Pool.

So, let's pretend now that ans is a Pool **, because that's what it is. In that case:

*ans = NULL;

This would now set the pointer that ans is pointer to, to a NULL. Not some manufactured instance of the Pool class. But a pointer to it.

But there's a bigger problem here:

 void* ans = malloc(nbytes + 4);  // overallocate by 4 bytes
 *(Pool**)ans = NULL;             // use NULL in the global new
 return (char*)ans + 4;           // don't let users see the Pool*

This is very old code, and it will work only if pointers are 4 bytes long.

On modern 64 bit platforms, with 8 byte-long pointers, this whole thing will fail miserably...

2 Comments

It is not "odd". It is code that assumes all pointers to an object are 4-bytes (aka 32 bit, typically) and associated alignment. The 4 would probably be better rewritten as sizeof(Pool *). Or remove it entirely - the offset is more of a dirty trick (a programmer trying to add a pseudo-safety measure), and doesn't achieve much of real use. (If removing it, also modify other functions that directly manipulate the Pool, as they will also use the offset).
@Peter Sam typed "OLD" not "ODD".

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.