Important to say in computer memory there are no integers or pointers - only sequence of bytes. It's your program interpreting these bytes as int
or int*
. Outside of your program it's just bytes
Let's look at your example step by step. Suppose for the demonstration purpose you have memory of 256 bytes and your int
s are only 1 byte:
step 1:
where you are declaring and assigning a variable
program:
int x = 1;
...
say your compiler decided to allocate memory cell 12 for the variable x
:
memory |address:value| (value of X means unknown):
|0:X|1:X|2:X| ... |12:1| ... |255:X|
step 2:
where you are declaring the pointer
program:
int x = 1;
int *p;
...
recall step 1 ... and compiler allocated memory cell 5 for variable p
(note - p
is not initialized yet so the value in the cell 5 is X
!):
memory |address:value| (value of X means unknown):
|0:X|1:X|2:X| ... |5:X| .. |12:1| ... |255:X|
step 3:
where you are assigning the pointer
program:
int x = 1;
int *p;
p = &x;
...
recall step 2 ... and now cell p
stores pointer to x
. we know cell 12 has been allocated for x
, hence 12 is stored into p
(cell 5):
memory |address:value| (value of X means unknown):
|0:X|1:X|2:X| ... |5:12| .. |12:1| ... |255:X|
step 4:
where you are declaring the second pointer
program:
int x = 1;
int *p;
p = &x;
int *p2;
...
recall step 3 ... and your compiler allocated memory cell 6 for the variable p2
(it's unassigned yet so the value is X
):
memory |address:value| (value of X means unknown):
|0:X|1:X|2:X| ... |5:12|6:X| .. |12:1| ... |255:X|
step 5:
where you are assigning the second (still undefined!) pointee:
program:
int x = 1;
int *p;
p = &x;
int *p2;
*p2 = *p;
...
recall step 4 and then *p
makes compiler recall that p
is cell 5 so it generates insruction to read from cell 5 (there is value 12) and use that value as an address of an actual result: reading from cell 12
returns 1. Now compiler knows that name p2
has been given to cell #6 p2
is 6, but *p2
means you need to read the value of p2 and use it as address of the actual storage. But in our case this value is X
(undefined)! However in the actual memory chips there is no undefined values and something has been store there at boot time (say value 42
). Things become more complicated if virtual memory is affected but i do not want to discuss it here. So let's say electrical charges and cosmic rays at boot time initialized this cell to a random value of 42
. Let's see what will happen when *p2 = *p
will be executed: we've seen how *p
returned us 1, now program will try to find where to store it to. it will read value of p2
which yields 42, now program will store 1
to the memory cell 42
and the result will look:
memory |address:value| (value of X means unknown):
|0:X|1:X|2:X| ... |5:12|6:X| .. |12:1| ... |42:1| ... |255:X|
in real world however using uninitialized value would frequently lead to a crash. When virtual memory involved some areas of address space (our 256 addresses) may have no memory cells allocated for them and writing to such an address would lead to crash.
step 6:
where you are assigning the second pointer and make it defined pointing again to x
:
program:
int x = 1;
int *p;
p = &x;
int *p2;
p2 = p;
...
recall step 4 and ignore step 5 which caused your program crash ... now compiler knows which cells are used for both p
(cell 5) and p2
(cell 6). hence assignment p2=p
works this way: take value from p
(which is 12), do not interpret it as pointer but just copy it to p2
instead. Now p2
becomes initialized with the pointer to x
(cell 12).
memory |address:value| (value of X means unknown):
|0:X|1:X|2:X| ... |5:12|6:12| .. |12:1| ... |42:X| ... |255:X|
step 7:
where you are assigning pointer to unnamed location:
program:
int x = 1;
int *p;
p = &x;
int *p2;
p2 = (int*)100;
...
... let's focus on this line: p2 = (int*)100;
this shows how to assign pointer to point to an unnamed memory cell. Recall that compiler didn't assign a name yet to cell #100. But what if you know for sure there is something interesting at this memory location, for example a computer device is communicating with us via that cell. What if the keyboard stores the next character to memory cell #100 and we would like to read it? Now you can read it as *p2
:
memory |address:value| (value of X means unknown):
|0:X|1:X|2:X| ... |5:12|6:100| .. |12:1| ... |42:X| ... |100:keyboard| ... |255:X|
step 8:
where you are revealing relation of pointers to arrays:
program:
int x = 1;
int *p;
p = &x;
int a[5];
a[2]=18;
int *p2 = a;
p2[2]=3;
...
... let's focus on this line: int a[5];
: this declaration line tells compiler to allocate 5 cells and use name a
for them. And compiler allocated cells 7, 8, 9, 10, 11 for a
:
memory |address:value| (value of X means unknown):
|0:X|1:X|2:X| ... |5:12|6:100|7:X|8:X|9:X|10:X|11:X|12:1| ... |42:X| ... |100:keyboard| ... |255:X|
note that it didn't allocate a pointer -- a
would be used literally like any other variable! This is how a[2]=18
works: first compiler knows that a
starts and 7
, so it uses that value as a start, then it adds 2
to get 9
. So the target storage cell is found - it's the cell number 9, so it store 18 into that cell:
|0:X|1:X|2:X| ... |5:12|6:100|7:X|8:X|9:18|10:X|11:X|12:1| ... |42:X| ... |100:keyboard| ... |255:X|
so let's see how int *p2 = a;
works: the compiler knows that a
starts at 7. Hence 7 is stored into p2
(recall the cell #6 has been allocated to p2
at step 4)
|0:X|1:X|2:X| ... |5:12|6:7|7:X|8:X|9:18|10:X|11:X|12:1| ... |42:X| ... |100:keyboard| ... |255:X|
after initializing a pointer you can use it as an array p2[2]=3;
: this is short for *(p2 + 2) = 3
. value of p2
is 7, adding 2 gives 9, so we are storing 3 to cell #9:
|0:X|1:X|2:X| ... |5:12|6:7|7:X|8:X|9:3|10:X|11:X|12:1| ... |42:X| ... |100:keyboard| ... |255:X|
Sorry for such a long explanation i tried to cover relations between values, variables, pointers and arrays and most use cases for these.