본문 바로가기

Programming/C++ Basic

[Basic C++] 10 - What is a pointer in C++ (1/2)

In this post, I am going to talk about the definition of a pointer and its usage in C++. You might already know what a pointer is; a pointer is a variable that holds the address of a variable. A pointer does not only store an address of a simple variable such as int, char, and double, but also an array, function, and even class, struct, and union. There are more than three kinds of pointers in C++ such as a smart pointer, unique pointer, and double/triple pointer, however, there is no need to understand all of them right now.

 

I had a hard time to get the hang out of it since the very concept does not exist in Python. I have been learning programming since 2017 and my first language was Python 3. Python was a very convenient language for a complete novice programmer, but most of the complicated low-level processes are automated.

 

Each language has its own procs and cons and the underlying idea of each language is also very different. I hope you can come to a full understanding of a pointer in C++ gradually with these posts. In order to understand this post, you need to read and understand these posts concerning reference and memory in C++ first.

 


Table of contents

1. What is reference/address

2. What is a pointer

3. Pointer calculation

4. Appendix


1. What is reference/address

 

A reference is an address of a variable and you can access it by a reference operator & (Ampersand). The address of a simple variable like int shows where it is allocated, but it only returns the address of the first element when it comes to complex data structures like an array. Figure 1 is a nice example to understand it.

 

Code example 1

#include <iostream>

int main()

{

       int a = 10;

       int b[] = { 1, 2, 3, 4 ,5 };

       std::cout << "The address of a = "<<&a << std::endl;

       for (int i = 0; i < 5; ++i)

       {

             std::cout << "The address of b" << "[" << i << "] =" << &b[i] << std::endl;

       }

}

Figure 1 The memory address of an int variable and int array

 

 


2. What is a pointer

 

In Figure 1, the variables were declared as local variables and they were allocated in the stack memory. As we already know well about the stack memory, whatever variables we declare go to the stack and is reclaimed automatically at the end of their lifetimes.

 

However, programmers often need to control the lifetime of local variables depending on the algorithm of the running program. The necessity of the heap memory appears here. The problem is that you can not allocate variables on the heap directly. There must be a tool connecting a user and the variables. The tool is, what we call, A Pointer.

 

Of course, you can declare a pointer that points to a variable allocated in the stack memory using a reference operator. However, a pointer is mostly used for dynamic allocation on the heap. Code example 2 shows the syntaxes of declaring a pointer and dynamic allocation.

 

 

Code example 2

[1] The syntax of a pointer is the same with the reference operator * (Asterisk).

Type * Identifier;

[2] The syntax of dynamic allocation needs the keyword “new”.

new type(value);

[3] Finally, dynamic allocation of a variable on the heap is below,

Type *Pointer_identifier = new type(value);

[4] You can also point a variable on the stack with the reference operator

Type *Pointer = & identifier

 

Look at Figure 2. P1 and P2 are pointers pointing to the variables allocated on the heap. 

 

Figure 2 Stack and heap memory allocation

 

Figure 3 is a good example showing how pointers work in C++. Here I declared two pointer variables, p1 and p2. The pointer variables, p1 and p2, point to the dynamic values, 10 and 20. The expression Point to means the pointer contains the address of the dynamic variable. Therefore,

 

Code example 3

#include <iostream>

int main()

{

       int* p1;

       int* p2;

       p1 = new int(10);

       p2 = new int(20);

       std::cout << "-------Pointer P1---------" << std::endl;

       std::cout << "Pointer P1 (Stack) = " << p1 << std::endl;

       std::cout << "Address of Pointer P1 (Stack) = " << &p1 << std::endl;

       std::cout << "What P1 points to (Heap) = " << *p1 << std::endl;

       std::cout << "What P1 points to (Heap address) = " << &*p1 << std::endl;

       std::cout << "-------Pointer P2---------" << std::endl;

       std::cout << "Pointer P2 (Stack) = " << p2 << std::endl;

       std::cout << "Address of Pointer P2 (Stack) = " << &p2 << std::endl;

       std::cout << "What P2 points to (Heap) = " << *p2 << std::endl;

       std::cout << "What P2 points to (Heap address) = " << &*p2 << std::endl;

}

Figure 3 Stack and heap memory allocation example

Code example 3 - explanation

cout << p1 is the address of dynamic value 10

cout << &p1 is the address of the pointer variable on the stack pointing to 10.

cout << *p1 is the dynamic value itself.

cout << &*p1 is the address of the dynamic value 10

 

The problem is C++ does not reclaim the heap memory as it does for the stack memory. This is fully logical because the compiler never knows when to delete the dynamic variables unless you explicitly specify it. Only the usage of a dynamically allocated variable is over, the pointer to the variable disappears from the stack memory. In other words, the variable on the heap is left behind. This is why you need to delete the variable manually and it's called "Garbage collection".

 

The keyword "delete" is used for this process.

// Dynamic allocation
int *p = new int (10);

// Delete the variable from the heap
delete p;

3. Pointer calculation

 

The same arithmetic calculation for normal variables applies to pointers in C++ but only addition and subtraction operations are allowed. The behavior of such pointer calculation is slightly different from the one we are used to depending on the size of pointers. 

 

One example to explain this very well is a pointer and an array. See code example 4. A pointer variable "p" and an int array [5] are declared in the main function. The pointer "p" points to the first element of the array.

 

Code example 4

#include <iostream>

int main()
{
	int* p;
	int array[] = { 1,2,3 };
	p = &array[0];

	std::cout << "The address of array = " << array << std::endl;
	std::cout << "The address of array[0] = " << &array[0] << std::endl;
	std::cout << "The address of p = " << &p << std::endl;
	std::cout << "The value of p = " << p << std::endl;
	std::cout << "--------------------"<< std::endl;
	std::cout << "The address of p+1 = " << p+1 << std::endl;
	std::cout << "The value of p+1 = " << *(p + 1) << std::endl;
}

 

Figure 4 Result of code example 4

The result of code example 4 clearly tells us that the address of an array is the address of its first element. Plus, a pointer addition operation increases its value by the size of its data type. In this example, the data type was "int", thus it increased by 4 bytes. The thing is, an array is a group of values allocated in series. By adding 4 bytes to the pointer "p", it points to the second element of the array, "2" (Figure 5). This works the same for char array (String).

 

Figure 5 Pointer addition operation