CommonLounge Archive

C++: Pointers and Memory Allocation

September 21, 2018

Memory address

Every variable you define is stored in memory and thus, has a location or address. Just like you live in your home and your home has an address, similarly variables also need a home, which is memory location in this case and their home also has an address.

That address can be accessed using the ampersand operator (&) (also called the address-of operator), which denotes an address in memory. For example:

int score = 5;
cout << &score << endl;
// Output 0x7ffd30e25934

This outputs the memory address of the variable score.

The memory address is not like your home address, which would be very big. Instead, the address is simply a hexadecimal number (a number in base 16). In base 10, the digits of a number are 0-9. In base 16, each digit can take the following 16 values: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E and F. In C++, the prefix 0x is added to let you know that the number is a hexadecimal number. The value of the number starts after the 0x, which in this case is 7ffd30e25934.

Pointers

You already used pointers in previous tutorials, but let’s understand it more thoroughly now. As you already know, pointers are also variables, but they have a different type. Pointers are variables which store a memory address.

Declaring a pointer

We know that pointers are variables, and that variables need to be declared. So, let’s see how to declare a pointer:

int *ip;     // pointer to an integer
double *dp;  // pointer to a double
float *fp;    // pointer to a float
char *ch;    // pointer to a character

The asterisk (*) sign is used to declare a pointer.

As you know, the asterisk * sign is also used for multiplication. When * is used with an int or a float, it does multiplication. When it is used with (on the left of) a pointer, it deferences it.

Just as with variables, we give the pointer a name and define the type. Here’s the general syntax:

data_type *pointer_name;

Assigning pointers a value

Again, just as with variables, we can assign the pointer some value. For example:

int score = 5;
int *scorePtr;
scorePtr = &score;
cout << &score << endl; 
// Output "0x29fee8"
cout << scorePtr << endl; 
// Output "0x29fee8"

Here, &score gives address of score variable and scorePtr stores the address of score.

Dereference

The same way we use the & operator to get the address of a variable, we can use the * operator to get the value stored at the address to which the pointer is currently pointing. So:

// display value of variable, 
cout << score << endl; 
// Output 5 
// display value at memory location to which pointer is pointing
cout << *scorePtr << endl; 
// Output 5 

The asterisk (*) is used in declaring a pointer for the simple purpose of indicating that it is a pointer. Don’t confuse this with the dereference operator, which is used to obtain the value located at the specified address. They are simply two different things represented with the same sign.


In the above example, &score is equivalent to scorePtr. And *scorePtr is equivalent to score. We verified this by printing out the values. But this is also true in a slightly more general sense. Let’s see how:

score = score + 4;
cout << score << endl; 
// Output 9 
score = *scorePtr + 4;
cout << score << endl; 
// Output 13
*scorePtr = *scorePtr + 4;
cout << score << endl; 
// Output 17
*scorePtr = score + 4; 
cout << score << endl; 
// Output 21

All of the above three statements are same, and in each case, the value of score is increased by 4. We can access the variable by dereferencing the variable’s pointer.

As scorePtr is pointing to the variable score, dereferencing the pointer (*scorePtr) is representing exactly the same as the variable score. This remains true as long as we don’t change what scorePtr is pointing to.

int otherScore = 8; 
scorePtr = &otherScore; // Now scorePtr points to otherScore
*scorePtr = *scorePtr + 4;
cout << otherScore << endl; 
// Output 12
cout << score << endl; 
// Output 21 (score is unchanged)

Memory Allocation

Static & Dynamic Memory

In a C++ program, memory is divided into two parts:

The stack: All of your local variables that you define in your program, take up memory from the stack.

The heap: Heap memory is used when the program allocates memory dynamically. Dynamically here means that the user has power to allocate new memory for a particular variable during run-time.

Many times, you are not aware in advance how much memory you will need to store particular information in a defined variable. But we can get this information of the size of variable while your program is running. That’s why, during run-time of your program, we allocate memory through new operator:

new int;

This allocates the memory size necessary for storing an integer on the heap, and returns the address of that location. If we want, we can store the address in a pointer. Example:

int *p = new int;
*p = 5;

We have dynamically allocated memory for an integer, and assigned it a value of 5.

The pointer p is stored in the stack as a local variable, and holds the heap’s allocated address as its value. The value of 5 is stored at that address in the heap.

For the local variables which are defined in stack memory, memory deallocation (free-ing up the memory) is done automatically. But we have to take care during allocating memory in heap, because it won’t be deallocated automatically as it happens for stack memory. That’s where delete operator comes in handy.

delete pointer;

This statement releases the memory pointed to by pointer. For example:

int *p = new int; // request memory
*p = 5; // store value
cout << *p << endl; // use value
// Output 5
delete p; // free up the memory

Forgetting to free up memory that has been allocated with the new keyword will result in memory leaks, because that memory will stay allocated until the program shuts down.

So why should we use dynamic memory allocation?

Here are some situations when dynamic memory allocation is useful:

  • If you don’t know how much memory our program needs to allocate at compile-time. Dynamic allocation allows your code to naturally request memory piece by piece at any moment and only when you need it.
  • When you want that the memory allocation to persist after the function returns. In general, a variable is only accessible from within the scope it was defined in. So if a variable is defined inside a function, it can only be accessed from inside the function.
void func() {
    int a;
    cout << a << endl;
}
// You can't use 'a' outside the curly brackets. 
// This is called the scope of the variable.

Dangling Pointers

The delete operator frees up the memory allocated for the variable, but it does not delete the pointer itself, as the pointer was declared as a variable in stack memory.

These pointers are not pointing towards any memory location, and thus are called dangling pointers.

For example:

int *p = new int; // request memory
*p = 5; // store value
delete p; // free up the memory
// now p is a dangling pointer
p = new int; // reuse for a new address

NULL Pointers

The NULL pointer is pointer which is assigned to NULL. The value of pointer is 0 and it’s a good practice to assign NULL to a pointer variable when you declare it, in case you do not have exact address to be assigned. For example:

int *ptr = NULL;
cout << ptr << endl;
// Output 0
cout << *ptr << endl;
// error - Runtime Error!

The value of pointer is 0, but when you try to dereference it, it will give you an error because it’s pointing towards NULL which doesn’t have any value.

Dynamically memory allocation for arrays

We can also dynamically allocate memory for arrays by:

int *p = new int[20];

The above syntax has allocated memory for 20 elements for array named p.

p stores the address of the array (or first element of array).

int *p = new int[20];
cout << p << endl;
cout << &p[0] << endl;
/* Output
0x1b37c20
0x1b37c20
*/

Iterating over arrays using pointers

For accessing address of other elements, you just have to use the arithmetic operator +, to move the pointer ahead. Let’s print the addresses of the elements of array using loop:

int *p = new int[20];
for (int i = 0; i < 20; i++) {
    cout << p + i << endl;
}

This will print the addresses of the elements of array. Similarly, we can use pointers to access all the array element’s values.

int *p = new int[20];
for (int i = 0; i < 20; i++) {
    cout << *(p + i) << endl;
}

Conclusion

That’s it. You totally rock. Pointers and dynamic memory allocation is an advanced topic in C++, and you should pat yourself on the back for understanding this topic.


© 2016-2022. All rights reserved.