gbadev.org forum archive

This is a read-only mirror of the content originally found on forum.gbadev.org (now offline), salvaged from Wayback machine copies. A new forum can be found here.

C/C++ > How to send a pointer to a function, and back?

#121229 - LOst? - Sat Mar 10, 2007 5:17 pm

I have programmed a lot of C/C++ applications for years. So I have encountered this problem a few times. Sure I can handle it, but I want to know why it works this way.

So have a look:
Code:

char global_name [] = "Peter";

int GetName (char* name)
{
 name = global_name;

 // "name" is now pointing to &global_name [0]

 return 1;
}

int main (void)
{
char* name = NULL;

GetName (name);

// Now "name" == NULL! Why didn't it survive past GetName()?
}


You all know in C, you can send a pointer of a structure as an argument. And it will survive. But why can't my name pointer survive?

My temporary fix for these problems is declaring GetName like this:
Code:

int GetName (char*& name);


However this is a C++ syntax. What if I want to go back to C? That's why I want some help.
_________________
Exceptions are fun

#121234 - kusma - Sat Mar 10, 2007 6:09 pm

double pointers

Code:

int GetName (char** name);

GetName(&name);

#121240 - sgeos - Sat Mar 10, 2007 7:06 pm

Let me put it this way. It is exactly the same with pointers:
Code:
#define NULL_ID    0
#define GLOBAL_ID  7

#define NG  0
#define OK  1

int gId = GLOBAL_ID;

int getId(int pId)
{
   pId = gId;
   // "pId" is now equal to gId

   return OK;
}

int main (void)
{
   int id = NULL_ID;

   getId(id);
   // Now "id" == NULL_ID!  Why didn't it survive past getId()?
}

Clearly you would use a pointer to int to solve this problem. If you want any value to survive a subroutine call, you need to pass a pointer to that value, because all of the variables local to the subroutine are destroyed when is returns.

The following does not (usually) work because local variables are destroyed when the function returns:
Code:
char *getName(void)
{
   char name[MAX_SIZE] = "Evil Frog Man";
   return name;
}

You need to use some form of the following to retain values after functions return.
Code:
*pointer = address;

This even holds for pointers.
kusma wrote:
double pointers

Also known as pointer to pointer. Try something like this:
Code:
char gName[MAX_SIZE] = "Evil Frog Man (chocolate rice version, bonus sprinkles)";

int getName(char* pName[])
{
   *pName = gName;
   return OK;
}

-Brendan

#121241 - keldon - Sat Mar 10, 2007 7:21 pm

I think you might first want to know how you expect the function to work. Will the function place the name characters into the name pointer sent in the getName method? Double pointers is one way to achieve it, it all depends on how getName is supposed to work. Is it retrieving it from somewhere, is new memory allocated, I am currently under the assumption that getName is returning a constant so that should not be an issue.

#121245 - poslundc - Sat Mar 10, 2007 7:41 pm

Others have already explained the solutions (double pointers, or have the function allocate space and return the pointer to you). For an explanation of the problem, you just need to realize that in C you are always passing by value, not by reference, so you won't be able to use reference semantics unless you explicitly pass a reference in (which in C, means passing in a pointer to whatever you want to be modifiable).

Your code doesn't work as you expect it for the exact same reason this code doesn't behave in that way:

Code:
void foo(int a)
{
    a = 5;
}

void main(void)
{
    int a = 0;

    foo(a);

    // Now "a" is still 0, even though foo changed its copy of a to 5.
}


When main() calls foo(), it passes "a" in by value, not by reference, so its local value for "a" doesn't change, even if foo changes its local copy of "a".

In the same way, when your version of main() passes "name" to GetName(), it passes it by value, not by reference, so GetName() can't change the value of "name". If "name" had pointed to something other than NULL, then it could change whatever data was in memory that "name" was pointing to, but it still wouldn't be able to change where "name" points to.

So in the same way I could make my foo() function alter "a" if I passed in a reference to "a" (by obtaining a pointer to it, for example, and passing in that pointer instead of the plain value), you could make GetName() alter "name" by passing in a reference to "name" (by obtaining a pointer to it, which, since "name" is already a pointer, would make your reference a double-pointer).

Dan.

#121246 - LOst? - Sat Mar 10, 2007 7:48 pm

Thank you kusma.

And very much appreciated for your explaination sgeos :)

The first problem occured to me when I was trying to create a set of help functions for a custom linked list I had created. I got very surprised by the pointer survival. And very confused when trying to debug the application to find where the value disappeared. I had to abort that linked list.

I had a function like this:
Code:

char* ptr = new char [10];

if (AddTail (ptr) != true)
{
 delete [] ptr;
}


Failing to keep track of the actual allocated memory, and the application was doomed to crash. AddTail had some functions of it own to locate the list tail. So it got even worse trying to find a solution.

Code:

bool AddTail (char*& ptr)
{
 if (SearchListAndAddPtr (ptr)) // I know I know. I just want to demonstrate how hard it gets once you get here
 {
   return true;
 }

 return false;
}

bool SearchListAndAddPtr (char*& ptr)
{
 lst* list = root;

 while (list->next != 0)
 {
   list = list->next;
 }

 // Allocate a new list node
 AllocateListNode (list);

 ....... whatever you get the point
}

void AllocateListNode (lst*& ptr)
{
 ptr = new lst;

 // Imagine when this ptr gets lost! Memory leak trouble
}


The code above is a invalid as the result I got when I tried to make this system in the first place. God thanks for C++ templates!


EDIT: Thanks poslundc! And nice to see you as a moderator here!
Yea, sent by value. I have worked a lot with I386 assembler. The values get pushed onto the stack before the function call:
Code:


xor ecx,ecx
mov dword [a],ecx  ; a = 0

mov eax,[a]
push eax ; Pushes the value of a onto the stack

call foo


_________________
Exceptions are fun

#121251 - sgeos - Sat Mar 10, 2007 8:20 pm

LOst? wrote:
The first problem occured to me when I was trying to create a set of help functions for a custom linked list I had created.

For what it's worth, I don't think dynamic allocation really needs to be used in games (if anyone disagrees, I disagree). #define MAX_SIZE works well. Statically allocated link lists certainly have their place, but you probably want to use indices instead of pointers.

LOst? wrote:
I got very surprised by the pointer survival.

Things will survive on the stack, but they can be clobbered at any moment. "But it worked" only means so much.

-Brendan

#121275 - gmiller - Sun Mar 11, 2007 1:02 am

Just remember that C/C++ are by default "pass-by-value" so the variable in the function is a copy (created on the stack) not the original. This allows you to change parameters without worrying about the values in the calling function (stack pointer is returned back to the pre-creation value when the function returns). Should you wish to change the value variable that was passes it needs to be done by passing the variables address in some way (reference or pointer) or by assigning the variable to the functions return value. There are pro's and con's to any approach you just need to decide what risks you are willing to take.

#121312 - LOst? - Sun Mar 11, 2007 1:32 pm

Oh I need help!

I get this error:
Code:

cannot convert parameter 1 from 'char (*)[260]' to 'char **'


I have a global array:
Code:

char global_name [260];


And I want to send it to a function, and I want the modification that this function did in that array to surive when the function returns:
Code:

int Function (char** array)
{
 *array [0] = 'A';
 *array [1] = '\0';

 return 1;
}

int main (void)
{
 Function (&global_name); // Error! global_name acts like a pointer, but seems to be something else

 if (global_array [0] == 'A')
  return 1; // Worked

 return 0; // Didn't work
}



Chaning "Function" declaration doesn't help:
Code:

int Function (char* array []);


... Still gives a simular error:
Code:

cannot convert parameter 1 from 'char (*)[260]' to 'char *[]'


I am stuck unless I want to make a local ptr to point to global_array, and then send it in as a **parameter. And that can end up in a mess. Note that this "Function" is a help routine to avoid many simular routines doing the same work
_________________
Exceptions are fun


Last edited by LOst? on Sun Mar 11, 2007 5:01 pm; edited 1 time in total

#121324 - sgeos - Sun Mar 11, 2007 4:28 pm

I could give you the answer, but you wouldn't learn anything and this is important. Look at this code:
Code:

int Function (char** array)
{
 array [0] = 'A';
 array [1] = '\0';

 return 1;
}

What type is the variable array?

-Brendan

#121328 - LOst? - Sun Mar 11, 2007 5:00 pm

sgeos wrote:
I could give you the answer, but you wouldn't learn anything and this is important. Look at this code:
Code:

int Function (char** array)
{
 array [0] = 'A';
 array [1] = '\0';

 return 1;
}

What type is the variable array?

-Brendan


Sorry I just missed the *:
Code:

*array [0] = 'A';

But that was a spelling error by me. The error appears when calling this function, and not inside the function.

To answer your question:
array is of type "pointer to a pointer to a char".
Note that I have almost never worked with double pointers. Exceptions are such as sending a &LPDIRECTDRAW object to a COM method, or the int main (char* arg [],..... - int main (char** arg... That's why I know so little of how they work.

The error has something to do with the global_name being an array and not a pointer. But seriously I don't know how to fix this.
_________________
Exceptions are fun

#121332 - sgeos - Sun Mar 11, 2007 6:14 pm

If all you need to do is write to a buffer, you want to use pointer to character, and not char **.

Use char ** when you want to do something like this:
Code:
void setName(char **pString, int pId)
{
        switch (pId)
        {
                case NAME_A:
                        *pString = gNameA;
                        break;
                case NAME_B:
                        *pString = gNameB;
                        break;
                default:
                        *pString = gNameDefault;
                break;
        }
}


-Brendan

#121337 - LOst? - Sun Mar 11, 2007 6:52 pm

sgeos wrote:
If all you need to do is write to a buffer, you want to use pointer to character, and not char **.

Use char ** when you want to do something like this:
Code:
void setName(char **pString, int pId)
{
        switch (pId)
        {
                case NAME_A:
                        *pString = gNameA;
                        break;
                case NAME_B:
                        *pString = gNameB;
                        break;
                default:
                        *pString = gNameDefault;
                break;
        }
}


-Brendan


Ok. Thanks sgeos
_________________
Exceptions are fun

#121340 - LOst? - Sun Mar 11, 2007 7:18 pm

I found out a good way to pass an array into a function using a C++ function template. This works with Visual C++ 2005. Maybe it works in gcc too?

Code:

template <size_t Size>
int DoSomethingWithBuffer (char (&buffer) [Size])
{
 for (unsigned int i = 0; i < Size; i++)
 {
  buffer [i] = xxxxxxxxxxx
 }

 return 0;
}

char global_buffer [20];

int main (void)
{
 DoSomethingWithBuffer (global_buffer);

 return 0;
}


Sending just a pointer to a buffer, and you must pass its size too. I have no idea how this template works. For some reason <Size> is getting filled with 20 automatically. Very nice :)

And thank you all for the help on pointers to pointers!
_________________
Exceptions are fun

#121344 - sgeos - Sun Mar 11, 2007 8:07 pm

LOst? wrote:
Sending just a pointer to a buffer, and you must pass its size too.

Good plan. You can use a vanilla pointer and a size in C.
Code:
void myfunction(type_t *pBuffer, int pSize)
{
  int i;

  for (i = 0; i < pSize; i++)
    // do something
}


LOst? wrote:
I have no idea how this template works.

I would fix that if I were to use this template.

Quote:
For some reason <Size> is getting filled with 20 automatically. Very nice :)

My guess is that this "template" is just the C++ "modify array by reference" calling syntax.

LOst? wrote:
And thank you all for the help on pointers to pointers!

Very welcome. Good luck.

-Brendan

#121347 - tepples - Sun Mar 11, 2007 8:23 pm

sgeos wrote:
LOst? wrote:
Sending just a pointer to a buffer, and you must pass its size too.

Good plan. You can use a vanilla pointer and a size in C.
Code:
void myfunction(type_t *pBuffer, int pSize)
{
  int i;

  for (i = 0; i < pSize; i++)
    // do something
}

In fact, this is an incredibly common idiom in C, even in the standard library. Look at memset(), qsort(), and fwrite().
_________________
-- Where is he?
-- Who?
-- You know, the human.
-- I think he moved to Tilwick.