Part 2 begins…
So, here we are. The second section of the C language’s pointer concept. In this post, we’ll take a closer look at how pointers can mimic arrays by referring to their addresses on the memory. Also, handling memories is something crucial and a privilege of C programmers. Great powers come with great responsibility. Let’s dive into the deeper journey of the legendary language, shall we?
This is the second installment in my C language tutorial. Since I’m still learning the language, so this post also works as my daily progress tracker. So, let’s begin the journey!
Pointer and array: basics
In the program below3 (sample_ex3-1.c), there are four variables:
int/int* | char/char* | |
Regular variable | ar1 | ar2 |
Pointer variable | p1 | p2 |
Here, what we want to show you is that pointer variables can also be array variables as well.
Here is the output result. As you can see, by adding 1, the pointer variables also add up to its value accordingly.
Let’s put the breakpoint in line 13.
This is the variables’ initial status. p1 and p2 are initialized as null, so the initial values are zero.
ar1 and ar2 are expanded. Since they are also initialized, random values are substituted in the variables.
When you step over once, zero is substituted in the variable i.
And the zero is substituted in ar1[0] in the second step over.
A is substituted in ar2[0] in the third step over. (65 is the ASCII number for the upper case A).
Here is the result when you finish executing stepovers until the end of the ar1 and ar2 values.
variables | values |
ar1 | 0 to 4 |
ar2 | A to E |
And when you stop your stepover at the beginning of the p1-p2 loop, you can confirm that the very beginning address digits of p1 and p2 are exactly the same as that of ar1 and ar2. It’s because the p1 and p2 is referring to the memory addresses of ar1 and ar2, instead of their values.
Change your IDE perspective to watch, and type p1. Then you can see the aforementioned memory address.
By adding one by one, values are also increased accordingly. What you can confirm here is that p1[0] to p1[4] is equal to ar1[0] to ar1[4].
Since p1[5] is out of the array range, it doesn’t work properly.
The logic is the same in p2. By adding one by one, the alphabet is increased accordingly. by adding a number to the pointer variable, it refers to its counterpart in the ar1/ar2 array.
The relationship between pointer and array
What’s important to know is that an array is an unusual version of a pointer. In the example below (sample_ex3-2.c), the pointer variables p1 and p2 work as the array variable d.
And also what’s important here is that by substituting an array variable for a pointer variable, the pointer can act like an array.
Here is the output result. The results are all the same, but how they printout vary.
Here, the breakpoint is inserted in line 8.
Here are some key points in image 14:
1: the below syntax in line 11 is similar to that of a pointer despite the fact that d is indeed an array. But this syntax is okay.
*(d + i)
2: Line 11’s below syntax is similar to that of an array despite the fact that p1 is a pointer. And still, it’s okay.
p1[i]
3: In line 11, the p2 indicator is incremented in line 12.
*p2 //line 11
p2++ //line 12
The first stop in the breakpoint indicates that the values from line 5 are inserted in the array variable d.
After executing stepovers a couple of times, you can see that p1 and p2 are now referring to the same address of d.
Switching to the watch perspective, you can confirm the same values from d by typing through p1[0] to p1[2].
d is also referring to the same address as shown in the above image. And you can confirm that by typing *d and *(d+1) and such.
Since p2 is updated (incremented) by p2++ in line 12, you can’t list all the values of the variable. At this point in the image below, p2 is currently stopped at &d[1].
This is the current output result.
When you finish executing the stepovers, you can see that p2 reached its endpoint.
Just remember, an array is a special version of a pointer.
Generating and deleting memories
In the previous section, we confirmed the fact that pointers can work as arrays. Here, in the last section of this post, we’ll see that pointers can be arrays themselves. This happens when we dynamically generate pointers. So, what does it mean dynamically? Well, it means you can generate pointers whenever/wherever you like, and delete them whenever/wherever you like.
Here is the output result where the program just prints out the values. What’s important here is the fact that we haven’t generated actual values in the program.
In line 11 and 12, we’ve actually generated arrays. The malloc function stores a specified number of memories according to line 4 – SIZE = 3. So, in this case, we’ve generated 3 arrays. And, (int*) and (double*) cast the generated arrays into pointer-type.
What’s important to learn here is that malloc generates memories in the heap area, where you can generate data whenever/wherever you like. And memories generated in the area must be librated at some point within the program.
In line 23 and 24, the free function librates the memories. What we need to remember is that malloc and free always come together.
To see how the program works, I inserted the breakpoint in line 11.
In the first stop, 0 is substituted as they are initialized in line 7 and 8.
In the first stepovers, memory addresses are substituted for p1 and p2.
Switching to the watch perspective, you can see that all the values inside p1 and p2 are just initialized.
By stepping over, you can confirm that 0 and 0.0 are substituted for p1[0] and p2[0] respectively.
As you step over till the end, you can see that all the values are now substituted for arrays.
What we need to be careful of is the scary fact that values out of the arrays’ ranges can also be stored in the arrays. in this case, it might be impossible to free those areas in the memory. And worse it could destroy the memory system.
Here, by inserting another breakpoint in line 23, we can see how memory areas will be librated.
As you execute stepovers, you can confirm that p1’s memories are now liberated.
And p2’s memories are now also librated.
Lastly, we need to learn something important about memories. The regular variables declared in line 7 and 8 are stored in the stack area, while the malloc variables in line 11 and 12 are stored in the heap area. And the qualities of the two different memories are a little different.
The stack area’s memories are automatically liberated when the main program processes are finished. This means that the memories in the stack area are out of your control.
On the other hand, heap area’s memories are allowed to be generated by programmers whenever/wherever they like, but also they must be manually deleted within the program process, meaning they are under your control.