Clarify passing ptrs to ptrs vs pass by ptrs

closed account (4ET0pfjN)
I thought I understood passing by ptrs to ptrs (pointers to pointers) vs pass by pointers, but now I'm not sure. Below are the same function that clears an array to elem 0 in each slot. What I'm confused about is why does the pass by pointer work, I thought whenever we need to modify the original object, we have to point to the original pointer, so need to pass by pointers to pointers. If arrays can just pass by pointers, why is it then that for let's say inserting a node at front of linked list, we have to use in function param for e.g. node *insert_at_front(string data, node **front);, why can't is just be: node *insert_at_front(string data, node *front). I know the latter is a copy of address, but why do I have to use pointers to pointers to update actual front pointer, but no need with the clearArray using arrays? I mean, if it's a copy of address (I'm referring to the latter insert_at_front), then shouldn't the rest of the node's addresses be the same, but just copies?

Using pointers to pointers in function parameters:
==================================================
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#define len 5
int *clearArray_1(int **boxes)
{
   int *box_copy = *boxes;//box_copy pts to first elem of array: box
   for (int len_ = len; len_ > 0; box_copy = (box_copy) + 1, --len_)
	if ( *(box_copy) != 0 )
	   *box_copy = 0;
			
   return *boxes;
}

int main()
{
   int *box = new int[len];
   *box = 33; 
   *(box + 1) = 44; 
   *(box + 2) = 11; 
   *(box + 3) = 99, 
   *(box + 4) = 888;
	
   clearArray_1(&box);
	
   for(int i = 0; i < len; ++i, box = box + 1)
      cout << *box << " , ";
	
   delete[] box; box = NULL;
   return 0;
}


VS

Using pointers in function param:
=================================
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#define len 5

int *clearArray_2(int *boxes)
{
   int *box = boxes;//box pts to first elem of array: box
   for (int len_ = len; len_ > 0; box = (box) + 1, --len_)
	if ( *(box) != 0 )
	   *box = 0;
			
   return boxes;
}

int main()
{
   int *box = new int[len];
   *box = 33; 
   *(box + 1) = 44; 
   *(box + 2) = 11; 
   *(box + 3) = 99, 
   *(box + 4) = 888;
	
   clearArray_2(box);//NB: array name is implicit pointer to first elem of array box
	
   for(int i = 0; i < len; ++i, box = box + 1)
      cout << *box << " , ";
	
   delete[] box; box = NULL;
   return 0;
}
Last edited on
What I'm confused about is why does the pass by pointer work, I thought whenever we need to modify the original object, we have to point to the original pointer


You're getting your wires crossed here. If you want to modify x in another function, you pass a pointer to x.

You only need a pointer to a pointer to x if you are modifying the pointer:

1
2
3
4
5
6
7
8
9
10
11
12
void ptr(int* p);
void ptrptr(int** p);

int main()
{
  int v = 5;
  int* p = &v;

  ptr(&v);  // can modify v
  ptr(p);  // same as ptr(&v). can modify v, but not p (ie: can't make 'p' point to something else)
  ptrptr(&p);  // can modify v and p (can make it point to something other than v)
}


why can't is just be: node *insert_at_front(string data, node *front)


It can be.



As for your example, it's a little hard to explain because you're doing a dynamic allocation, but I'll try:

 
int *box = new int[len];


Here, new int[len]; created an unnamed array. For purposes of this explanation, I will call this array U. U exists somewhere in memory, but because it has no name you can't access it directly. Instead, new gives you a pointer to it, which you are assigning to box.

Therefore, box points to U.

*box = 33;
Since box points to U, this line modifies the first element in the U array.

1
2
3
int *clearArray_1(int **boxes)
//...
clearArray_1(&box);


Here, we are effectively saying int** boxes = &box;. That is, we are making a pointer called boxes, which points to our pointer box, which points to U.

boxes -> box -> U

 
int *box_copy = *boxes;//box_copy pts to first elem of array: box 


This line basically undoes the above (&box). We make a new pointer box_copy, which equals whatever boxes points to. Since boxes points to box... we are effectively saying int* box_copy = box;. And since box pointed to U, box_copy now also points to U.

So we have:
boxes -> box -> U
box_copy -> U



The clearArray_2 version is similar:

1
2
3
int *clearArray_2(int *boxes)
//...
clearArray_2(box);//NB: array name is implicit pointer to first elem of array box 


Firstly, your comment is incorrect. 'box' is not an array name, it's a pointer. U is the array, and it has no name because it's unnamed. box just happens to point to it.

That said, by passing box to the function, we are effectively saying int* boxes = box;, which means both boxes and box will point to the same thing: U.
I think your confusion goes back to your linked list example, where you needed to maintain the values in the head/tail pointers. I'll give an example at the end where passing the pointer by value is not good enough.

First, let's deal with this example. Here's our memory layout.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
box  ┌─────┐
1000 │2000 │
     └─────┘

     ┌─────┐
2000 │  33 │
     ├─────┤
2004 │  44 │
     ├─────┤
2008 │  11 │
     ├─────┤
200A │  99 │
     ├─────┤
2010 │ 888 │
     └─────┘


box has address 1000 and points to an array of 5 elements at address 2000. The array has values { 33, 44, 11, 99, 888 }.

I've rewritten the body of the functions for simplicity.

In the first example:
1
2
3
4
5
6
void clearArray(int **boxes)
{
  int *box = *boxes;//box_copy pts to first elem of array: box
  for (int i = 0; i < 5; ++i)
    box[i] = 0;
}
boxes has value 1000.
box has value 2000.
As i changes for each loop, box[i] takes values 2000, 2004, 2008, 200A, 2010.

In the second example:
1
2
3
4
5
6
void clearArray(int *boxes)
{
  int *box = boxes;//box_copy pts to first elem of array: box
  for (int i = 0; i < 5; ++i)
    box[i] = 0;
}
boxes has value 2000.
box has value 2000.
As i changes for each loop, box[i] takes values 2000, 2004, 2008, 200A, 2010.

As you can see, both pointers are eventually pointing to the same memory block. That's why it works.

Let's look at a new function. grow() grows the array. But as it changes the underlying memory block, you can't just pass the pointer by value because you need to update it's value back in the caller function.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <stddef.h>

void grow(int **pbox, size_t oldsz, size_t newsz)
{
  if (oldsz < newsz)
  {
    // allocate the new larger block
    int *newblock = new int[newsz];

    // copy over elements
    for (size_t i = 0; i < newsz; ++i)
      if (i < oldsz)
        newblock[i] = (*pbox)[i];
      else
        newblock[i] = 0;

    // release old block
    delete [] (*pbox);
    *pbox = newblock;
  }
}

int main()
{
  int *box = new int[5];
  grow(&box, 5, 10);
  return 0;
}
Last edited on
closed account (4ET0pfjN)
But when we pass by pointers (so just the array name which is actually a pointer to first elem), aren't we passing by value still meaning we are modifying a copy of the actual pointer (in main) pointing to first elem. So once I call clearArray, am I not just changing all elements in the COPY of the array to value 0s, which means the values are not changed in the actual array? Really appreciate everyone's help
Last edited on
The pointer is being passed by value... so the function receives a copy of the pointer. The array is not really being passed at all.

But yes... changes made to the pointer will change the copy of the pointer, but when you do something like box[0] = 5; you aren't changing the pointer, you're changing the data the pointer points to.

I'll try and whip up some diagrams to see if I can illustrate this more clearly.


EDIT:

Here's a diagram. I really think seeing visually how this is working will help:

http://i48.tinypic.com/1h80hi.png

Remember, 'box' is not the array. Box is a pointer. The array does not actually have a name because it was made anonymously with new. I think you are getting confused because you are thinking that 'box' and the array are one and the same.
Last edited on
There is no COPY of the array. The array is only in one location in memory. What is copied is that value of the pointer, which is the address of the memory of the array.

Assuming you are talking about the second example, when clearArray_2 is called, it de-references the address, so it accesses the array of memory that was created in main. Both the pointer "box" in main and the pointer "boxes" in clearArray_2 point to the same memory, so when clearArray_2 modifies it, main sees the modified version.

By the way, in kbw's explanation, I believe he meant:
As i changes for each loop, box[i] takes values located at 2000, 2004, 2008, 200A, 2010.
in both locations because box is dereferenced by the [] operator.
closed account (4ET0pfjN)
I will read the comments contributed, thanks and appreciate your diagram, Disch. But for this simple function, why can't I use a pointer to a pointer.

1
2
3
4
5
6
7
8
9
10
11
12
13
void addTwo_v4(int **y)
{
   **y += 2;
}

int main
{
   int y = 88;

   addTwo_v4(&y);
   
   return 0;
}


Why would this not work?
Last edited on
This won't work because &y is an int*, not an int**. However, if you did:

1
2
3
4
5
6
7
8
9
int main
{
   int y = 88;
   int* yPtr =&y;

   addTwo_v4(&yPtr);
   
   return 0;
}


it would work.

It's overkill, though. You only need to pass a pointer if you are modifying the contents of something. You need to pass a pointer-to-a-pointer when you want to modify the pointer itself.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int x = 1;
int y = 2;

void setToY(int** ptrPtr)
{
    *ptrPtr = &y;
}

int main()
{
    int* ptr = &x;  // ptr points to the memory named "x"

    setToY(&ptr);   // no ptr points to the memory named "y"
}

This does not work because you pass incorrect type to the function.

&y has type int *

while the parameter of function declared as

int **
Last edited on
Topic archived. No new replies allowed.