Pointers 4 slides

Dynamic memory, memory management, pointers as return types.
Mithat Konar
October 23, 2021

Dynamic memory allocation

  • Dynamic memory allocation allows you to reserve blocks of computer memory at runtime.
  • Typically used with pointers.

The new operator

  • new <data-type>
    • reserves a block of memory to hold the specified <data-type>
    • returns the base address of that block.
    • optional parenthesis around <data-type>
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 points to a double that isn't associated with a variable identifier.

Example

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.
  • Warning: If there is not enough memory available to allocate the desired block, BadThings™ will happen.
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;
}

Memory Management

Memory management of regular variables

  • The management of memory associated with regular local variables is automatic.
  • Regular local variables are destroyed when the lifetime of the scope where they are declared ends.
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;  // localVar is destroyed at end of fcn call
    cout << localVar << endl;
}

Memory management of dynamically allocated storage

  • Dynamically allocated memory is not automatically managed.
  • 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 = nullptr;  // localPtr is destroyed at end of fcn call
    localPtr = new int;       // but not dynamically allocated storage
 
    *localPtr = 99;
    cout << *localPtr << endl;
}

Memory leaks

  • The previous is an example of a small memory leak.
  • A bigger leak:
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;
}

Memory leaks

  • Memory leaks, no matter how small, are bad programming practice.
  • Can be fixed by the proper use of deallocation: releasing back to the OS storage that was previously dynamically allocated.
  • Deallocation of dynamically allocated storage does not happen automatically.
    • You must explicitly (i.e., manually) deallocate the memory.

The delete operator

  • The delete operator lets you explicitly deallocate memory that has been dynamically allocated.
int *myPtr = new int;
...
delete myPtr; // deallocates block pointed to by myPtr.
  • Principle: All dynamically allocated memory must be deallocated somewhere in the program.
    • “For every new a delete.”

Example

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.
}

Deallocating arrays

Use square brackets to deallocate dynamically allocated arrays: 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 pools of RAM.
  • stack: a pool of memory whose allocation and deallocation is automatically managed.
    • Local and global variables are allocated from the stack.
  • heap: 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 present discussion.

''malloc'' and ''free''

  • malloc and free can also used be used to allocate and deallocate storage.
  • Part of C and so are available in C++ as well.
  • More cumbersome than new and delete, so 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;
}

Returning Pointers from Functions

  • Do not return a pointer to a local variable in a 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
    • dynamically allocated memory
    • nullptr.

Example

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.
  • You need to #include <memory>.
  • One smart pointer is the unique_ptr:

unique_ptr<type_pointed_to> pointer_name( expression_to_allocate_memory )

Example

ptr will be automatically deleted when the function returns.

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;
}

Example

Dynamically allocating a large block of managed memory.

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

  • Other smart pointers are weak_ptr and shared_ptr.
  • Not covered here.