What is pointer
A tutorial for C language beginners.
Pointer is an address
As we all know, a variable or object always has a memory address, either on stack or on heap.
For example, we suppoese to write a demo1 program like this:
1 |
|
The result on my machine is:
1 |
|
Analysis of demo1
The &i
is used to get the address of i
, and %p
is formatted in hexadecimal form.
So 0x16fbe348c
is the address of i
on my machine in this execution, and it’s probably not going to be this address when I do another execution or when you do an execution. It doesn’t matter, as long as there is an output. :)
Then there is a relationship between a value and its address:
1 |
|
On the other hand, if we have a memory address, it’s easy to read and write the value on this address, as if we can accurately find the target building according to a particular street address.
Now we can bring in the concept of pointer.
We may frequently encounter something like int* i_ptr = &i
. That’s the pointer. However, we currently have at lease two questions to explain:
- How to understand it in our mind?
- How does pointer relate to the example above?
In order to clearly answer them, let’s take a look at another example:
1 |
|
The result on my machine is:
1 |
|
Tips:
- At this time I did another run, the address of
i
is0x16db8f48c
which is different with that as0x16fbe348c
in demo1. It’s normal and I won’t repeat it later. - The purpose of using
%p
to output the value ofi_ptr
is to output the content in hexadecimal. It’s convenient to compare the address ofi
and the value ofi_ptr
.
Analysis of demo2
It’s easy to find that the address of i
and the value of i_ptr
are the same, 0x16db8f48c
.
Based on current information, we can imagine a relationship like this:
1 |
|
What we have just mentioned above is that, if we have a memory address, we can easily access the value on this address. In that way, since the value of i_ptr
is actually the address of i
, we can access i
by *i_ptr
. In other words, is’s exactly equivalent between *i_ptr = 2;
and i = 2;
!
Now we are able to establish a connection between address and pointer.
Pointer is also a value
Have you found that a variable is essentially a value on a address either a normal integer i
or a pointer i_ptr
?
Every line of C language code would be compiled and transformed to assembly. Actually, there is no concept of data type in memory from assembly’s perspective. Only values one by one in memory space. That’s it.
We can say int
is a value, float
is a value, as well as that int*
even int**
is also a value. Anyway, it’s only a value on a address no matter how the data type changes.
Wait! Wait a moment! What is int**
just mentioned?
Let’s see a new example before we explain that. It’s an example we may encount when learning the usage of functions in C language.
1 |
|
The result is:
1 |
|
We would like to change the value of i
within the function change
, but unfortunately i
weren’t changed…
The textbook would teach you to change it to something like demo4:
1 |
|
The result would be as expected:
1 |
|
Could you find the exact reason?
Let’s take a quiz and see what demo5 outputs:
1 |
|
There is a memory leak in demo5. It’s just for demonstration reference.
A little hint:
Look back the memory model about value and address we learned just now.
The actual result is that a segmentation fault
occured. If you could have foreseen this, you certainly had understood it and you wouldn’t have to read on. :)
Analysis of demo3
Firstly, let’s analyze demo3.
In memory, the i
in main
like this:
1 |
|
When entering the function change
, a copy of i
is actually copied out as i_in_change
.
1 |
|
Note that the address of i_in_change
is b
here and the address of i
is a
. It means they are different variable indeed. After we changed i
by i = 2;
, it became like:
1 |
|
But the i
in main
hasn’t changed at all, so the effect of modifying i
in main
hasn’t been achieved.
Analysis of demo4
Let’s look at demo4 again.
At the beginning, it’s consistent with demo3. The memory space of i in main is as follows:
1 |
|
When entering the function, i_ptr
is as follows:
1 |
|
Note that the value of i_ptr
(a
) is the address of i
.
After *i = 2;
, i_ptr
didn’t change but i
was modified:
1 |
|
We modified i
in main
successfully by indirect access of the pointer!
Analysis of demo5
Then look at demo5.
i
in main
is as follows:
1 |
|
i_ptr_in_func
is a copy of i_ptr
after entering the function func
:
1 |
|
After i_ptr_in_func = (int*)malloc(sizeof(int));
, it became as follows:
1 |
|
i_ptr_in_func
changed but i_ptr
kept remains. Therefore, for i_ptr
it’s equal to the code as follows:
1 |
|
Obviously it’s unavailable.
We can find that after passed into a function, the pointer can only modify the value of the pointed object, and it does not make much sense to modify its own value.
What if we just want to allocate space for int* i
in main
in the function func
? Check out demo6 below:
1 |
|
By analyzing the memory structure from demo1 to demo5, hope you can figure out why demo6 works properly. :)
Analysis of demo6
Tips:
Its transformation in memory model is almost entirely indentical to demo4.
Pointer and Array
We all know an array is a contiguous amount of memory space. For example, int arr[3];
is like this:
1 |
|
arr
is actually an address. Based on it, we’ll quickly think of associating pointers with arrays in this way int* p = arr;
. Further on, we can use this pointer p
to read or modify a particular value of the array by p[0]
p[1]
p[2]
. If x
is an index, p[x]
means the value offseting some units from the basic address. It’s equivalent to *(p + x)
even *((int*)((char*)p + x * sizeof(int)))
. That’s it. An arithmetic offset. No magic.
Since p[x]
is just the value based on the address accumulated by p
and x
, x[p]
is also available. It’s supported by most compilers, but it’s a heresy, not a best practice. It’s best to know this, but it’s best not to use it. :)