lo tolmencre lo tolmencre - 1 month ago 5x
C++ Question

casting pointer to pointer... to pointer?

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;
is a void pointer, so I would assume it is cast to a
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

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


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 *).