There are two ways to think about this — the "high level" way and the "low level" way.
High level: I like to use labeled boxes to represent variables and other objects. If I say
int i = 5;
then I have an int-shaped box, named i
, containing the value 5:
+-------+
i: | 5 |
+-------+
If I then say
int *p = &i;
then I have a pointer-shaped box, named p
, containing a pointer that points at variable i
:
+-------+
i: | 5 |
+-------+
^
|
+--|----+
p: / * /
+-------+
If I then say
p = malloc(5 * sizeof(int));
and if malloc
succeeds, then p
now contains a pointer pointing at some new, unnamed memory which malloc
gave me:
+-------+
p: / * /
+----|--+
|
v
+-------+
| |
+-------+
| |
+-------+
| |
+-------+
| |
+-------+
| |
+-------+
That's the high-level way. For the low-level way, I'm going to think about pointers not as those pretty little arrows, but as actual, numeric memory addresses.
I typed in this program:
#include <stdio.h>
int main()
{
int i = 5;
printf("i: %p: %d\n", &i, i);
int *p = &i;
printf("p: %p: %p: %d\n", &p, p, *p);
}
When I ran it on my computer, I got this output:
i: 0x7ffee74ed9cc: 5
p: 0x7ffee74ed9c0: 0x7ffee74ed9cc: 5
So I can draw a different version of the box-and-arrow picture, showing actual addresses:
+-----+
0x7ffee74ed9cc: | 5 |
+-----+
+--------------+
0x7ffee74ed9c0: | 7ffee74ed9cc |
+--------------+
Now suppose I do
p = malloc(5 * sizeof(int));
p[0] = 11;
p[1] = 22;
p[2] = 33;
p[3] = 44;
p[4] = 55;
printf("p: %p: %p: %d %d %d\n", &p, p, *p, p[1], p[2]);
Now it prints
p: 0x7ffee74ed9c0: 0x7fbd11500000: 11 22 33
so I can draw this picture:
+--------------+
0x7ffee74ed9c0: | 7fbd11500000 |
+--------------+
+------+
0x7fbd11500000: | 11 |
+------+
0x7fbd11500004: | 22 |
+------+
0x7fbd11500008: | 33 |
+------+
0x7fbd1150000c: | 44 |
+------+
0x7fbd11500010: | 55 |
+------+