Pointer to pointer question

Started by
5 comments, last by L. Spiro 10 years, 3 months ago

Take the directx 9 CreateIndexBuffer function,these are the args:



HRESULT CreateIndexBuffer(
  [in]           UINT Length,
  [in]           DWORD Usage,
  [in]           D3DFORMAT Format,
  [in]           D3DPOOL Pool,
  [out, retval]  IDirect3DIndexBuffer9 **ppIndexBuffer,
  [in]           HANDLE *pSharedHandle
);

What I did,was: I created a pointer to pointer:

IDirect3DIndexBuffer9 **pp;

And used it in the function call.But I get an exception,if I change it,so I create only a pointer(not a pointer to pointer) variable:

IDirect3DIndexBuffer9 *pp;

and pass it as an argument in the form &pp,it works.

Why?

Advertisement

By taking the address of pp with &pp, you are creating a pointer to pp's type. If pp is a pointer to a pointer, then taking its address results in a pointer to a pointer to a pointer. If pp is a pointer, then taking its address produces a pointer to a pointer, which is what you want. Output parameters like that tend to accept a pointer to what it is going to return. In this case, it seems to be returning a pointer, and wants a pointer to a pointer so that it can dereference it later, assign the pointer value, and you get it after calling the function by passing the address of your pointer variable.

Hope that helps.

you got it wrong,when I had IDirect3DIndexBuffer9 **pp,i simply passed it as pp,not &pp.

A common method for returning multiple values in C++ is to "return" values by writing to parameters that were passed by reference. In your case, CreateIndexBuffer returns an HRESULT as the function's return value, but also returns a IDirect3DIndexBuffer9* via a parameter passed by reference. In order to pass a IDrect3DIndexBuffer9* by reference, the function must either take a IDirect3DIndexBuffer*& (a C++ reference to a pointer) or take a IDrect3DIndexBuffer** (a pointer to a pointer). You can imagine the function CreateIndexBuffer is implemented like this:


// very pseudo code follows

HRESULT CreateIndexBuffer(
  /*[in]*/           UINT Length,
  /*[in]*/           DWORD Usage,
  /*[in]*/           D3DFORMAT Format,
  /*[in]*/           D3DPOOL Pool,
  /*[out, retval]*/  IDirect3DIndexBuffer9 **ppIndexBuffer,
  /*[in]*/           HANDLE *pSharedHandle
)
{
    /* do some d3d9 stuff */
    IDirect3DIndexBuffer9 *pBuffer = new ConcreteDirect3DIndexBuffer9(/*etc*/);

    if (pBuffer != NULL) {
        // now assign pBuffer to *ppIndexBuffer to 'return' this value
        *ppIndexBuffer = pBuffer;  // dereference ppIndexBuffer and set its value
        return S_OK;
    }

    return E_FAIL;
}

Now consider what happens when you call CreateIndexBuffer two different ways:


IDirect3DIndexBuffer9 **pp;
CreateIndexBuffer(etc, pp);  // CreateIndexBuffer will dereference pp, but pp is an uninitialized variable, and the dereference will fail


// if you call it like this
IDrect3DIndexBuffer9 *p;
CreateIndexBuffer(etc, &p);  // CreateIndexBuffer will dereference &p, giving just p, which can then be written to

you got it wrong,when I had IDirect3DIndexBuffer9 **pp,i simply passed it as pp,not &pp.

Even so, the latter half of my post applies. The function is trying to dereference the pointer to write to what it points to. If you are passing an uninitialized pointer to a pointer, then the program will crash when it tries to write to it.

you got it wrong,when I had IDirect3DIndexBuffer9 **pp,i simply passed it as pp,not &pp.

Think of what is wanted conceptually. The call wants to give you a pointer to IDirect3DIndexBuffer9. It needs the address of a pointer in order to modify it. Because it will modify the pointer at that address, you must give it the address of a real pointer.

You need a pointer:


IDirect3DIndexBuffer9* indices;

Done. Now you need a function to modify that pointer. Not modify the object that the pointer refers to (which currently is some random location in memory), but the pionter itself. So you need the address of that pointer, which is stored in a pointer to the thing you're taking the address of. The address of a pointer is stored in a pointer to a pointer.


IDirect3DIndexBuffer9** indices_address = &indices;

Now you can pass that to the function. It can dereference that pointer-to-pointer and hence be able to modify the location in memory that the original pointer refers to. That's what it wants to do. It wants to create and object and then make your original pointer refer to that object, hence it needs to modify the pointer itself (and needs its address in memory to do so).

Pointers refer to addresses. To modify a specific value in memory, you need its address. To modify what a pointer refers to, you need its address. Don't even start thinking about syntax or variables until you think about the concept of what you're trying to do. Then you can work forward to figure out the correct syntax and interfaces to achieve that goal, rather than guessing at syntax and then having to work backward to figure out why it didn't work.

Sean Middleditch – Game Systems Engineer – Join my team!

Part 1

The method wants to give you back a pointer to an object.

An IDirect3DIndexBuffer9 *.

Internally it is going to allocate an object and set the pointer you give it to that pointer.

Look at this:

void GiveYouMyPointer( IDirect3DIndexBuffer9 * _pd3dib9Out ) {
    IDirect3DIndexBuffer9 * pd3dib9Allocated = new IDirect3DIndexBuffer9();
    _pd3dib9Out = pd3dib9Allocated;
}
 
IDirect3DIndexBuffer9 * pd3dib9IndexBuffer = NULL;
GiveYouMyPointer( pd3dib9IndexBuffer );

What is pd3dib9IndexBuffer after this?

The answer is NULL. GiveYouMyPointer() changed a copy of your pointer. It didn’t actually make your pointer point to anything else.

There are 2 ways to do that correctly:

void GiveYouMyPointer( IDirect3DIndexBuffer9 * &_pd3dib9Out ) {
    IDirect3DIndexBuffer9 * pd3dib9Allocated = new IDirect3DIndexBuffer9();
    _pd3dib9Out = pd3dib9Allocated;
}

Now your pointer is passed as a reference, so changing it inside GiveYouMyPointer() actually changes your pointer from NULL to something else.

void GiveYouMyPointer( IDirect3DIndexBuffer9 ** _pd3dib9Out ) {
    IDirect3DIndexBuffer9 * pd3dib9Allocated = new IDirect3DIndexBuffer9();
    (*_pd3dib9Out) = pd3dib9Allocated;
}

This is exactly the same as passing by reference, except it requires a manual dereference inside GiveYouMyPointer(). Either way it is able to modify the pointer you actually passed it from NULL to something else.

This is what is happening inside those methods and why you have to pass things that way. Microsoft just chose to use 2 pointers instead of a reference.

Part 2

So why does this not work?

IDirect3DIndexBuffer9 ** ppd3dib9IndexBuffer = NULL;
GiveYouMyPointer( ppd3dib9IndexBuffer );

Because:

void GiveYouMyPointer( IDirect3DIndexBuffer9 ** _pd3dib9Out ) {
    IDirect3DIndexBuffer9 * pd3dib9Allocated = new IDirect3DIndexBuffer9();
    (*_pd3dib9Out) = pd3dib9Allocated; // ERROR: First pointer points to NULL/garbage.  Bad dereference.
    // Same as (*NULL) = pd3dib9Allocated;
}

L. Spiro

I restore Nintendo 64 video-game OST’s into HD! https://www.youtube.com/channel/UCCtX_wedtZ5BoyQBXEhnVZw/playlists?view=1&sort=lad&flow=grid

This topic is closed to new replies.

Advertisement