143

I was following this tutorial about how does a pointer to a pointer work.

Let me quote the relevant passage:


    int i = 5, j = 6, k = 7;
    int *ip1 = &i, *ip2 = &j;

Now we can set

    int **ipp = &ip1;

and ipp points to ip1 which points to i. *ipp is ip1, and **ipp is i, or 5. We can illustrate the situation, with our familiar box-and-arrow notation, like this:

enter image description here

If then we say

    *ipp = ip2;

we've changed the pointer pointed to by ipp (that is, ip1) to contain a copy of ip2, so that it (ip1) now points at j:

enter image description here


My question is: Why in the second picture, is ipp still pointing to ip1 but not ip2?

matts
  • 6,738
  • 1
  • 33
  • 50
Blake
  • 7,367
  • 19
  • 54
  • 80
  • 6
    please, add the type of `ipp` when defining it, so your question is complete ;-) – zmo Feb 06 '14 at 14:00
  • The way you've lined up your pictures, the first row are all objects of type `int`, the second row are all `int *` and ipp is `int **`. When you say `*ipp = ip2;` you are reading and writing only on the `int *` row of your diagram. – Brandin Feb 06 '14 at 14:07
  • Do you know how symlinks work? Pointers are sort of like symlinks (only you have to declare at compile time exactly how many layers of pointers you can have). – Blacklight Shining Feb 06 '14 at 23:59
  • Did you draw those diagrams by hand? If not, what tool did you use? – This isn't my real name Feb 07 '14 at 02:58
  • 1
    Because `ipp` pointing to `ip1` so `*ipp = ip2` is same as `ip1 = ip2` – Grijesh Chauhan Feb 08 '14 at 08:02
  • 1
    Can we please stop writing asteriks that far away from the pointed type? `int **ipp` is far less intuitive than `int** ipp`, especially when `**ipp` means a completely different thing outside of type declarations. – Darkhogg Feb 08 '14 at 17:14
  • 5
    @Darkhogg `int **ipp` seems quite intuitive for me. It means, I'm making `**ipp` an `int`. Which is true. – ithisa Feb 08 '14 at 17:27
  • @user54609 I have never seen it that way. Thinking `ipp` is a pointer to a pointer of `int` (aka `int**`) seems easier for me. – Darkhogg Feb 08 '14 at 17:31
  • 2
    @user54609 meanings and personal intuitiveness apart, consider this: The type of `ipp` is `int**`, so **just write `int**` instead of magical *"`imp` dereference is an int"* understandings**. – Manu343726 Feb 08 '14 at 18:28
  • 1
    @Manu343726 But on `int** a, b`, you don't get two pointers. So here it is better written as `int **a, b, *c, ***d`. (No, don't write this, it is badly readable. But if you'd ever would due to whatever reason, it would count this way.) – glglgl Feb 09 '14 at 07:55
  • because you did not change the value of `ipp` – Khaled.K Feb 12 '14 at 08:37

16 Answers16

144

Forget for a second about the pointing analogy. What a pointer really contains is a memory address. The & is the "address of" operator - i.e. it returns the address in memory of an object. The * operator gives you the object a pointer refers to, i.e. given a pointer containing an address, it returns the object at that memory address. So when you do *ipp = ip2, what you are doing is *ipp get the object at the address held in ipp which is ip1 and then assign to ip1 the value stored in ip2, which is the address of j.

Simply
& --> Address of
* --> Value at

gehho
  • 9,049
  • 3
  • 45
  • 59
Robert S. Barnes
  • 39,711
  • 30
  • 131
  • 179
  • 14
    & and * were never so easy – Ray Feb 07 '14 at 16:12
  • 7
    I believe the main source of confusion is due to the ambiguity of the * operator, which during variable declaration is used to indicate that the variable, in fact, is a pointer to a certain data type. But, on the other hand, it is also used in statements to access the contents of the variable pointed at by a pointer (dereferencing operator). – Lucas A. Feb 11 '14 at 20:33
43

Because you changed the value pointed to by ipp not the value of ipp. So, ipp still points to ip1 (the value of ipp), ip1's value is now the same as ip2's value, so they both point to j.

This:

*ipp = ip2;

is the same as:

ip1 = ip2;
Grijesh Chauhan
  • 57,103
  • 20
  • 141
  • 208
Skizz
  • 69,698
  • 10
  • 71
  • 108
  • 11
    May be worth pointing out the difference between `int *ip1 = &i` and `*ipp = ip2;`, i.e. if you remove the `int` from the first statement then the assignments look very similar, but the `*` is doing something very different in the two cases. – Crowman Feb 06 '14 at 14:04
22

Like most beginner questions in the C tag, this question can be answered by going back to first principles:

  • A pointer is a kind of value.
  • A variable contains a value.
  • The & operator turns a variable into a pointer.
  • The * operator turns a pointer into a variable.

(Technically I should say "lvalue" instead of "variable", but I feel it is more clear to describe mutable storage locations as "variables".)

So we have variables:

int i = 5, j = 6;
int *ip1 = &i, *ip2 = &j;

Variable ip1 contains a pointer. The & operator turns i into a pointer and that pointer value is assigned to ip1. So ip1 contains a pointer to i.

Variable ip2 contains a pointer. The & operator turns j into a pointer and that pointer is assigned to ip2. So ip2 contains a pointer to j.

int **ipp = &ip1;

Variable ipp contains a pointer. The & operator turns variable ip1 into a pointer and that pointer value is assigned to ipp. So ipp contains a pointer to ip1.

Let's sum up the story so far:

  • i contains 5
  • j contains 6
  • ip1 contains "pointer to i"
  • ip2 contains "pointer to j"
  • ipp contains "pointer to ip1"

Now we say

*ipp = ip2;

The * operator turns a pointer back into a variable. We fetch the value of ipp, which is "pointer to ip1 and turn it into a variable. What variable? ip1 of course!

Therefore this is simply another way of saying

ip1 = ip2;

So we fetch the value of ip2. What is it? "pointer to j". We assign that pointer value to ip1, so ip1 is now "pointer to j"

We only changed one thing: the value of ip1:

  • i contains 5
  • j contains 6
  • ip1 contains "pointer to j"
  • ip2 contains "pointer to j"
  • ipp contains "pointer to ip1"

Why does ipp still point to ip1 and not ip2?

A variable changes when you assign to it. Count the assignments; there cannot be more changes to variables than there are assignments! You start by assigning to i, j, ip1, ip2 and ipp. You then assign to *ipp, which as we've seen means the same as "assign to ip1". Since you didn't assign to ipp a second time, it didn't change!

If you wanted to change ipp then you'll have to actually assign to ipp:

ipp = &ip2;

for instance.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
21

hope this piece of code can help.

#include <iostream>
#include <stdio.h>
using namespace std;

int main()
{
    int i = 5, j = 6, k = 7;
    int *ip1 = &i, *ip2 = &j;
    int** ipp = &ip1;
    printf("address of value i: %p\n", &i);
    printf("address of value j: %p\n", &j);
    printf("value ip1: %p\n", ip1);
    printf("value ip2: %p\n", ip2);
    printf("value ipp: %p\n", ipp);
    printf("address value of ipp: %p\n", *ipp);
    printf("value of address value of ipp: %d\n", **ipp);
    *ipp = ip2;
    printf("value ipp: %p\n", ipp);
    printf("address value of ipp: %p\n", *ipp);
    printf("value of address value of ipp: %d\n", **ipp);
}

it outputs:

enter image description here

michaeltang
  • 2,850
  • 15
  • 18
13

My very personal opinion is that pictures with arrows pointing this way or that make pointers harder to understand. It does make them seem like some abstract, mysterious entities. They are not.

Like everything else in your computer, pointers are numbers. The name "pointer" is just a fancy way of saying "a variable containing an address".

Therefore, let me stir things around by explaining how a computer actually works.

We have an int, it has the name i and the value 5. This is stored in memory. Like everything stored in memory, it needs an address, or we wouldn't be able to find it. Lets say i ends up at address 0x12345678 and its buddy j with value 6 ends up just after it. Assuming a 32-bit CPU where int is 4 bytes and pointers are 4 bytes, then the variables are stored in physical memory like this:

Address     Data           Meaning
0x12345678  00 00 00 05    // The variable i
0x1234567C  00 00 00 06    // The variable j

Now we want to point at these variables. We create one pointer to int, int* ip1, and one int* ip2. Like everything in the computer, these pointer variables get allocated somewhere in memory too. Lets assume they end up at the next adjacent addresses in memory, immediately after j. We set the pointers to contain the addresses of the variables previously allocated: ip1=&i; ("copy the address of i into ip1") and ip2=&j. What happens between the lines is:

Address     Data           Meaning
0x12345680  12 34 56 78    // The variable ip1(equal to address of i)
0x12345684  12 34 56 7C    // The variable ip2(equal to address of j)

So what we got were just yet some 4 byte chunks of memory containing numbers. There's no mystical or magical arrows anywhere in sight.

In fact, just by looking at a memory dump, we can't tell whether the address 0x12345680 contains an int or int*. The difference is how our program chooses to use the contents stored at this address. (The task of our program is actually just to tell the CPU what to do with these numbers.)

Then we add yet another level of indirection with int** ipp = &ip1;. Again, we just get a chunk of memory:

Address     Data           Meaning
0x12345688  12 34 56 80    // The variable ipp

The pattern does seem familiar. Yet another chunk of 4 bytes containing a number.

Now, if we had a memory dump of the above fictional little RAM, we could manually check where these pointers point. We peek at what's stored at the address of the ipp variable and find the contents 0x12345680. Which is of course the address where ip1 is stored. We can go to that address, check the contents there, and find the address of i, and then finally we can go to that address and find the number 5.

So if we take the contents of ipp, *ipp, we will get the address of the pointer variable ip1. By writing *ipp=ip2 we copy ip2 into ip1, it is equivalent to ip1=ip2. In either case we would get

Address     Data           Meaning
0x12345680  12 34 56 7C    // The variable ip1
0x12345684  12 34 56 7C    // The variable ip2

(These examples were given for a big endian CPU)

AlphaGoku
  • 968
  • 1
  • 9
  • 24
Lundin
  • 195,001
  • 40
  • 254
  • 396
  • 5
    Though I take your point, there is value in thinking of pointers as abstract, mysterious entities. Any particular *implementation* of pointers is just numbers, but the implementation strategy you sketch is not a *requirement* of an implementation, it's just a common strategy. Pointers need not be the same size as an int, pointers need not be addresses in a flat virtual memory model, and so on; these are merely implementation details. – Eric Lippert Feb 06 '14 at 15:56
  • @EricLippert I think one can make this example more abstract by not using actual memory addresses or data blocks. If it was a table stating something like `location, value, variable` where location was `1,2,3,4,5` and value was `A,1,B,C,3`, the corresponding idea of pointers could be explained easily without the use of arrows, which are inherently confusing. With whatever implementation one chooses, a value exists at some location, and this is piece of the puzzle that becomes obfuscated when modeling with arrows. – MirroredFate Feb 06 '14 at 19:02
  • 1
    @EricLippert In my experience, most of the would-be C programmers who have problems understanding pointers, are those who were fed abstract, artificial models. Abstraction is _not_ helpful, because the whole purpose of the C language today, is that it's close to the hardware. If you are learning C but don't intend to write code close to the hardware, _you are wasting your time_. Java etc is a much better choice if you don't want to know how computers work, but just do high level programming. – Lundin Feb 07 '14 at 07:22
  • @EricLippert And yes, various obscure implementations of pointers may exist, where the pointers don't necessarily correspond to addresses. But drawing arrows aren't going to help you understand how those work either. At some point you have to leave the abstract thinking and get down to the hardware level, otherwise you should not be using C. There are many far more suitable, modern languages intended for purely abstract high-level programming. – Lundin Feb 07 '14 at 07:27
  • @Lundin: I'm not a big fan of arrow diagrams either; the notion of an arrow *as data* is a tricky one. I prefer to think of it abstractly but without arrows. The `&` operator on a variable gives you a coin that represents that variable. The `*` operator on that coin gives you back the variable. No arrows required! – Eric Lippert Feb 07 '14 at 14:44
  • Sidenote- MISRA C:2004 Rule 17.5 "The declaration of objects should contain no more than 2 levels of pointer indirection." Use of more than 2 levels of indirection can seriously impair the ability to understand the behaviour of the code, and should therefore be avoided. @Lundin- Ur answers are brilliant :) – AlphaGoku Apr 18 '16 at 06:14
  • @Vector9 Nobody has used more than 2 levels here, so I don't see how that is relevant? Also MISRA-C:2004 is outdated, time to upgrade to MISRA-C:2012. – Lundin Apr 18 '16 at 06:17
  • Was just pointing out to anyone who reads this not to use more than 2 levels. No arguments related to the concepts you have explained. Will get hold of 2012 :) – AlphaGoku Apr 18 '16 at 06:22
8

Notice the assignments:

ipp = &ip1;

results ipp to point to ip1.

so for ipp to point to ip2, we should change in the similar manner,

ipp = &ip2;

which we are clearly not doing. Instead we are changing the value at address pointed by ipp.
By doing the folowing

*ipp = ip2;

we are just replacing the value stored in ip1.

ipp = &ip1 , means *ipp = ip1 = &i,
Now, *ipp = ip2 = &j.
So, *ipp = ip2 is essentially same as ip1 = ip2.

Dipto
  • 2,720
  • 2
  • 22
  • 42
5
ipp = &ip1;

No later assignment has changed the value of ipp. This is why it still points to ip1.

What you do with *ipp, i.e., with ip1, does not change the fact that ipp points to ip1.

Daniel Daranas
  • 22,454
  • 9
  • 63
  • 116
5

Because when you say

*ipp = ip2

you're saying the 'object pointed by ipp' to point the direction of memory that ip2 is pointing.

You're not saying ipp to point ip2.

Grijesh Chauhan
  • 57,103
  • 20
  • 141
  • 208
5

My question is: Why in the second picture, ipp is still point to ip1 but not ip2?

you placed nice pictures, I'm going to try to make nice ascii art:

Like @Robert-S-Barnes said in his answer: forget about pointers, and what points to what, but think in terms of memory. Basically, an int* means that it contains the address of a variable and an int** contains the address of a variable that contains the address of a variable. Then you can use the pointer's algebra to access the values or the addresses: &foo means address of foo, and *foo means value of the address contained in foo.

So, as pointers is about dealing with memory, the best way to actually make that "tangible" is to show what the pointers algebra does to the memory.

So, here's your program's memory (simplified for the purpose of the example):

name:    i   j ip1 ip2 ipp
addr:    0   1   2   3   4
mem : [   |   |   |   |   ]

when you do your initial code:

int i = 5, j = 6;
int *ip1 = &i, *ip2 = &j;

here's how your memory looks like:

name:    i   j ip1 ip2
addr:    0   1   2   3
mem : [  5|  6|  0|  1]

there you can see ip1 and ip2 gets the addresses of i and j and ipp still does not exists. Don't forget that addresses are simply integers stored with a special type.

Then you declare and defined ipp such as:

int **ipp = &ip1;

so here's your memory:

name:    i   j ip1 ip2 ipp
addr:    0   1   2   3   4
mem : [  5|  6|  0|  1|  2]

and then, you're changing the value pointed by the address stored in ipp, which is the address stored in ip1:

*ipp = ip2;

the program's memory is

name:    i   j ip1 ip2 ipp
addr:    0   1   2   3   4
mem : [  5|  6|  1|  1|  2]

N.B.: as int* is a special type, I prefer to always avoid declaring multiple pointers on the same line, as I think the int *x; or int *x, *y; notation can be misleading. I prefer to write int* x; int* y;

HTH

zmo
  • 24,463
  • 4
  • 54
  • 90
4

If you add the dereference operator * to the pointer, you redirect from the pointer to the pointed-to object.

Examples:

int i = 0;
int *p = &i; // <-- N.B. the pointer declaration also uses the `*`
             //     it's not the dereference operator in this context
*p;          // <-- this expression uses the pointed-to object, that is `i`
p;           // <-- this expression uses the pointer object itself, that is `p`

Therefore:

*ipp = ip2; // <-- you change the pointer `ipp` points to, not `ipp` itself
            //     therefore, `ipp` still points to `ip1` afterwards.
moooeeeep
  • 31,622
  • 22
  • 98
  • 187
3

If you'd want ipp to point to ip2, you'd have to say ipp = &ip2;. However, this would leave ip1 still pointing to i.

Andrejovich
  • 544
  • 2
  • 13
3

Very beginning you set,

ipp = &ip1;

Now dereference it as,

*ipp = *&ip1 // Here *& becomes 1  
*ipp = ip1   // Hence proved 
Sunil Bojanapally
  • 12,528
  • 4
  • 33
  • 46
3

Considere each variable represented like this:

type  : (name, adress, value)

so your variables should be represented like this

int   : ( i ,  &i , 5 ); ( j ,  &j ,  6); ( k ,  &k , 5 )

int*  : (ip1, &ip1, &i); (ip1, &ip1, &j)

int** : (ipp, &ipp, &ip1)

As the value of ipp is &ip1 so the inctruction:

*ipp = ip2;

changes the value at the addess &ip1 to the value of ip2, which means ip1 is changed:

(ip1, &ip1, &i) -> (ip1, &ip1, &j)

But ipp still:

(ipp, &ipp, &ip1)

So the value of ipp still &ip1 which means it still points to ip1.

rullof
  • 7,124
  • 6
  • 27
  • 36
1

Because you are changing the pointer of *ipp. It means

  1. ipp (varaiable name)----go inside.
  2. inside ipp is address of ip1.
  3. now *ipp so go to (adress of inside) ip1.

Now we are at ip1. *ipp(i.e.ip1) = ip2.
ip2 contain address of j.so ip1 content will be replace by contain of ip2(i.e. address of j), WE ARE NOT CHANGING ipp CONTENT. THAT'S IT.

haccks
  • 104,019
  • 25
  • 176
  • 264
1

*ipp = ip2; implies:

Assign ip2 to the variable pointed to by ipp. So this is equivalent to:

ip1 = ip2;

If you want the address of ip2 to be stored in ipp, simply do:

ipp = &ip2;

Now ipp points to ip2.

HelloWorld123456789
  • 5,299
  • 3
  • 23
  • 33
0

ipp can hold a value of (i.e point to) a pointer to pointer type object. When you do

ipp = &ip2;  

then the ipp contains the address of the variable (pointer) ip2, which is (&ip2) of type pointer to pointer. Now the arrow of ipp in second pic will point to ip2.

Wiki says:
The * operator is a dereference operator operates on pointer variable, and returns an l-value (variable) equivalent to the value at pointer address. This is called dereferencing the pointer.

Applying * operator on ipp derefrence it to a l-value of pointer to int type. The dereferenced l-value *ipp is of type pointer to int, it can hold the address of an int type data. After the statement

ipp = &ip1;

ipp is holding the address of ip1 and *ipp is holding the address of (pointing to) i. You can say that *ipp is an alias of ip1. Both **ipp and *ip1 are alias for i.
By doing

 *ipp = ip2;  

*ipp and ip2 both points to same location but ipp is still pointing to ip1.

What *ipp = ip2; does actually is that it copies the contents of ip2 (the address of j) to ip1 (as *ipp is an alias for ip1), in effect making both pointers ip1 and ip2 pointing to the same object (j).
So, in the second figure, arrow of ip1 and ip2 is pointing to j while ipp is still pointing to ip1 as no modification is done to change the value of ipp.

haccks
  • 104,019
  • 25
  • 176
  • 264