User Tools

Site Tools


cplusplus:pointers_4

Pointers 4

Dynamic memory, memory management, pointers as return types.1)

Dynamic memory allocation

Dynamic memory allocation allows you to reserve blocks of computer memory at runtime and use them to store variable data. The declared blocks or memory are most often used with pointers.

The ''new'' operator

The new operator is used to allocate dynamic memory. new <data-type> reserves a block of memory large enough to hold the specified <data-type> and returns the base address of that block:

double *foo;      // pointer to a double
foo = new double; // allocate an unnamed block of memory
                  // large enough to hold a double
                  // and set foo to point to it.

foo now points to a double that is not associated with a regular variable identifier.

simple-allocation.cpp
/** Dynamically allocate and use two doubles. */
#include <iostream>
using namespace std;
 
int main()
{
    double *myPtr, *yourPtr;
 
    myPtr = new (double);
    yourPtr = new (double);
 
    cout << "Enter a number: ";
    cin >> *myPtr;
    cout << "Enter a number: ";
    cin >> *yourPtr;
 
    cout << "The average of the two numbers is " << (*myPtr + *yourPtr)/2.0 << "." << endl;
 
    return 0;
}

Dynamic allocation of arrays

Dynamic memory allocation can be used to allocate an array:

dynamic-array.cpp
/** Dynamically allocate and use an array. */
#include <iostream>
using namespace std;
 
int main()
{
    const int SIZE = 10;
    double *arrayPtr = new double[SIZE];  // create block to hold array
 
    /* You can use subscript notation [] to access an array: */
    for(int i = 0; i < SIZE; i++)
    {
        arrayPtr[i] = i * i;
    }
 
    /* or pointer arithmetic: */
    for(int i = 0; i < SIZE; i++)
    {
        cout << *(arrayPtr + i) << endl;
    }
 
    return 0;
}

If there is not enough memory available to allocate the desired block, BadThings™ will happen.

Memory Management

Memory management of regular variables

Regular local variables are destroyed when the lifetime of the scope where they are declared ends. In the example below, localVar is destroyed at the end of the function call along with the storage associated with it:

local-var-memory.cpp
/** Example showing local variable lifetime. */
#include <iostream>
using namespace std;
 
void ninetynine();
 
int main()
{
    ninetynine();
 
    // ... do some other stuff ... //
 
    return 0;
}
 
void ninetynine()
{
    int localVar = 99;
    cout << localVar << endl;
}

Thus, the management of memory associated with regular local variables is automatic.

Memory management of dynamically allocated storage

Dynamically allocated memory is not automatically managed in C++. In the example below, the variable localPtr is destroyed at the end of the function call, but the dynamically allocated storage for the int pointed to by localPtr is not.

memory-loss.cpp
/** Example showing lifetime of dynamically allocated storage and a 
  * a small memory leak.
  */
#include <iostream>
using namespace std;
 
void ninetynine();
 
int main()
{
    ninetynine();
 
    // ... do some other stuff ... //
 
    return 0;
}
 
void ninetynine()
{
    int *localPtr = new int;
 
    *localPtr = 99;
    cout << *localPtr << endl;
}

Because the pointer is the only way to access the allocated block, there is no way to do anything useful with the block after the function terminates and main enters the … do some other stuff … part of the program. The dynamically allocated memory block is still taking up space, and there's no way to make another pointer to point to that block.

Memory leaks

The above is an example of a memory leak. It's a tiny one: when the program is run, it uses 4 or 8 or whatever (i.e., sizeof(int)) more bytes of RAM after the function returns than it needs to. This may seem like nothing worth losing sleep over, but what if ninetynine() appears in a loop or is otherwise executed repeatedly?

memory-leak.cpp
/** Example of a sizable memory leak. */
#include <iostream>
using namespace std;
 
void ninetynine();
 
int main()
{
    for (int i=0; i<10000; i++)
    {
        ninetynine();
    }
 
    // ... do some other stuff ... //
 
    return 0;
}
 
void ninetynine()
{
    int *localPtr = new int;
 
    *localPtr = 99;
    cout << *localPtr << endl;
}

This adds up to a lot of wasted RAM. Memory leaks, no matter how small, are considered bad programming practice. Fortunately, they can be fixed by the proper use of deallocation.

Deallocation

Deallocation is the process of releasing back to the OS storage that was previously dynamically allocated.

Deallocation of dynamically allocated storage does not happen automatically in C++, therefore you must explicitly (i.e., manually) deallocate the memory. The delete operator lets you explicitly deallocate memory that has been dynamically allocated.

Any memory that you have dynamically allocated must be deallocated somewhere in the program. Otherwise a memory leak will result.

The code below fixes the memory leak introduced above:

deallocation.cpp
/** Example showing deallocation. */
#include <iostream>
using namespace std;
 
void ninetynine();
 
int main()
{
    ninetynine();
 
    return 0;
}
 
void ninetynine()
{
    int *localPtr = new int;
 
    *localPtr = 99;
    cout << *localPtr << endl;
    delete localPtr;  // deallocates block pointed to by localPtr.
}

To deallocate dynamically allocated arrays, use square brackets: delete [] arrayPtr;

Gaddis-Pr9-14.cpp
// This program totals and averages the sales figures for any
// number of days. The figures are stored in a dynamically
// allocated array.
#include <iostream>
#include <iomanip>
using namespace std;
 
int main()
{
    double *sales = nullptr,	// To dynamically allocate an array
    total = 0.0,				// Accumulator
    average;					// To hold average sales
    int numDays,				// To hold the number of days of sales
        count;					// Counter variable
 
    // Get the number of days of sales.
    cout << "How many days of sales figures do you wish ";
    cout << "to process? ";
    cin >> numDays;
 
    // Dynamically allocate an array large enough to hold
    // that many days of sales amounts.
    sales = new double[numDays];
 
    // Get the sales figures for each day.
    cout << "Enter the sales figures below.\n";
    for (count = 0; count < numDays; count++)
    {
        cout << "Day " << (count + 1) << ": ";
        cin >> sales[count];
    }
 
    // Calculate the total sales
    for (count = 0; count < numDays; count++)
    {
        total += sales[count];
    }
 
    // Calculate the average sales per day
    average = total / numDays;
 
    // Display the results
    cout << fixed << showpoint << setprecision(2);
    cout << "\n\nTotal Sales: $" << total << endl;
    cout << "Average Sales: $" << average << endl;
 
    // Free dynamically allocated memory
    delete [] sales;
    sales = nullptr; // Make sales a nullptr.
 
    return 0;
} 

heap vs. stack

Local variables and dynamically allocated storage come from different pools of RAM. The stack is a pool of memory whose allocation and deallocation is automatically managed. Local variables (and global ones too) are allocated from the stack. The heap is a pool of memory whose allocation and deallocation is explicitly managed. Dynamically allocated storage is allocated from the heap.

A more detailed discussion of the heap versus the stack, while important, is beyond the scope of the present discussion. But it is important to know that there are two different memory pools that C++ programs draw from.

''malloc'' and ''free''

malloc and free can also used be used to allocate and deallocate storage. They are part of C and so are available in C++ as well. However, they are more cumbersome than new and delete introduced in C++, and their use is discouraged.

Returning Pointers from Functions

A function can return a pointer:

returned-pointer.cpp
/** Returning a pointer from a function. */
#include <iostream>
using namespace std;
 
char* someChar();
 
int main()
{
    char *foo;
 
    foo = someChar();
 
    cout << *foo << endl;
 
    return 0;
}
 
char* someChar()
{
    char *myCharPtr = new char;
 
    *myCharPtr = 'a';
 
    return myCharPtr;
}

However, the function must not return a pointer to a local variable in the function—because that local variable will cease to exist after the function terminates.

Only return a pointer to:

  • data that was passed to the function as an argument, or
  • dynamically allocated memory, or
  • NULL.
Gaddis-Pr9-15.cpp
// This program demonstrates a function that returns
// a pointer.
#include <iostream>
#include <cstdlib>   // For rand and srand
#include <ctime>     // For the time function
using namespace std;
 
// Function prototype
int *getRandomNumbers(int);
 
int main()
{
   int *numbers;  // To point to the numbers
 
   // Get an array of five random numbers.
   numbers = getRandomNumbers(5);
 
   // Display the numbers.
   for (int count = 0; count < 5; count++)
      cout << numbers[count] << endl;
 
   // Free the memory.
   delete [] numbers;
   numbers = 0;
   return 0;
}
 
//**************************************************
// The getRandomNumbers function returns a pointer *
// to an array of random integers. The parameter   *
// indicates the number of numbers requested.      *
//**************************************************
 
int *getRandomNumbers(int num)
{
   int *arr = nullptr;	// Array to hold the numbers
 
   // Return null if num is zero or negative.
   if (num <= 0)
      return NULL;
 
   // Dynamically allocate the array.
   arr = new int[num];
 
   // Seed the random number generator by passing
   // the return value of time(0) to srand.
   srand( time(0) );
 
   // Populate the array with random numbers.
   for (int count = 0; count < num; count++)
      arr[count] = rand();
 
   // Return a pointer to the array.
   return arr;
}

Smart Pointers

C++11's smart pointers manage their own memory to help mitigate memory leaks. To use smart pointers, you need to #include <memory>.

One smart pointer is the unique_ptr:

unique_ptr<type_pointed_to> pointer_name( expression_to_allocate_memory )

Gaddis-Pr9-17.cpp
// This program demonstrates a unique_ptr.
#include <iostream>
#include <memory>
using namespace std;
 
int main() 
{
   // Define a unique_ptr smart pointer, pointing
   // to a dynamically allocated int.
   unique_ptr<int> ptr( new int );
 
   // Assign 99 to the dynamically allocated int.
   *ptr = 99;
 
   // Display the value of the dynamically allocated int.
   cout << *ptr << endl;
   return 0;
}

ptr will be automatically deleted when the function returns.

Dynamically allocating a large block of managed memory is demonstrated here:

Gaddis-Pr9-18.cpp
// This program demonstrates a unique_ptr pointing
// to a dynamically allocated array of integers.
#include <iostream>
#include <memory>
using namespace std;
 
int main() 
{
   int max;   // Max size of the array
 
   // Get the number of values to store.
   cout << "How many numbers do you want to enter? ";
   cin >> max;
 
   // Define a unique_ptr smart pointer, pointing
   // to a dynamically allocated array of ints.
   unique_ptr<int[]> ptr( new int[max]);
 
   // Get values for the array.
   for (int index = 0; index < max; index++)
   {
      cout << "Enter an integer number: ";
      cin >> ptr[index];
   }
 
   // Display the values in the array.
   cout << "Here are the values you entered:\n";
   for (int index = 0; index < max; index++)
      cout << ptr[index] << endl;
 
   return 0;
}

Other smart pointers are weak_ptr and shared_ptr. They won't be covered here.

1)
Portions adapted from: Gaddis, Tony. “Pointers.” In Starting Out with C++: From Control Structures through Objects. 8th ed. Boston: Pearson, 2015. 495-546.
cplusplus/pointers_4.txt · Last modified: 2019/03/31 22:35 by mithat

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki