Pointers and Dynamic Memory Allocation

Tom Kelliher, CS23

Mar. 10, 1997

Pointer Variables

Recall:

The rvalue of a pointer variable of type T is the lvalue of a variable of type T.

Consider the trivial example:

int i;
int j;
int *pi = &i;   // sets pi's rvalue to i's lvalue
*pi = 1;        // sets i to 1
pi = &j;        // sets pi's rvalue to i's lvalue
*pi = 2;        // sets j to 2

Assume:

  1. i is allocated 4 bytes at 0x1000 (lvalue)
  2. j is allocated 4 bytes at 0x1004 (lvalue)
  3. pi is allocated 4 bytes at 0x1008 (lvalue)
What are the rvalues during execution?

Pointer Assignment

Valid assignments:

  1. NULL
  2. Another pointer of the same type
  3. Lvalue of appropriately-typed variable
  4. Appropriate pointer expressions

Which of the assignments are valid/invalid?

int i;
int j;
char c;
double x;
int* ip;
char* cp;
float* fp;

ip = &i;
cp = NULL;
ip = cp;
ip = j;
fp = &x;
cp = &i;
cp = (char*)&i;
cp = &c;
cp += 12;
cp *= 2;

The most common mistake with pointer parameters:

void f(int *ip)
{
   *ip = 0;
}

void g(void)
{
   int i;

   f(i);
}
What error message from compiler?

Most common ``fix?''

Memory Segments for Data

  1. Stack segment --- local data
  2. Data segment --- static data, ``large'' arrays
  3. Heap segment --- dynamic data: ``nameless'' variables

Variable attributes:

  1. Lifetime
  2. Scope
  3. Visibility

Lifetime example:

int* makeInt(void)
{
   static i = 0;
   int* pi;
   pi = new int;
   assert (pi != NULL);
   return pi;
}   // Lifetime/Scope of i, pi, "new int"?

Allocating Memory from the Free Store

AKA heap segment

Two operators:

<type> can be any type --- primitive, derived, or even a class

Examples:

int* pi;
pi = new int;       // allocate a single int
*pi = 0;            // modify the new int
delete pi;          // de-allocate
*pi = 0;            // design error to reference de-allocated memory
delete pi;          // *serious* design error
pi = new int[10];   // allocate int array
pi[1] = 3;
delete [] pi;       // [] necessary to ensure proper de-allocation
                    // of array
pi = NULL;          // good programming practice
delete pi;          // no damage

// allocate a struct
employeeRecord* employee = new employeeRecord;
employee = NULL;   // memory leak

Relevant points:

  1. new <type> returns <type>*
  2. Don't reference something deleted
  3. Don't delete more than once
  4. Good design practice: NULL the operand of a delete
  5. (Because) OK to multiply delete NULL
  6. Don't permit memory leaks

Example: A Simple Array Class

client code:

#include <iostream.h>
#include "array.h"

int main()
{
   IntArrayClass array(10);   // array has 10 elements, all zero'ed
   int i;

   for (i = 0; i < array.size(); i++)   // fill the array
      array.set(i, i);

   for (i = 0; i < array.size(); i++)   // print
      cout << array.read(i) << endl;

   array.resize(20);   // 10 new elements

   for (i = 0; i < array.size(); i++)   // print the enlarged array
      cout << array.read(i) << endl;

   array.resize(5);  // shrink the array

   for (i = 0; i < array.size(); i++)   // print
      cout << array.read(i) << endl;

   return 0;
}

array.h:

class IntArrayClass
{
 public:
   IntArrayClass(int size);
   ~IntArrayClass(void);
   int size(void);
   int read(int index);
   void set(int index, int value);
   int resize(int newSize);

 private:
   int elems;
   int *ia;
};

array.cc:

#include <assert.h>
#include <stdlib.h>
#include "array.h"


// construct an int array with size elements, zero them

IntArrayClass::IntArrayClass(int size)
{
   int i;

   assert (size >= 0);

   elems = size;
   ia = new int[elems];
   assert (ia != NULL);

   for (i = 0; i < elems; i++)
      ia[i] = 0;
}


// de-allocate an array

IntArrayClass::~IntArrayClass(void)
{
   delete [] ia;
   ia = NULL;
}


// return number of elements in array

int IntArrayClass::size(void)
{
   return elems;
}


// return value of one element of the array

int IntArrayClass::read(int index)
{
   assert (0 <= index && index < elems);
   return ia[index];
}


// set value of one element of the array

void IntArrayClass::set(int index, int value)
{
   assert (0 <= index && index < elems);
   ia[index] = value;
}


// make the array larger or smaller
// making it smaller truncates elements from the end
// making it larger adds new 0 elements to the end

int IntArrayClass::resize(int newSize)
{
   int *ta;
   int i;
   int limit;

   assert (newSize >= 0);

   // allocate space for the new array, which may be larger *or* smaller
   // than the old array

   ta = new int[newSize];   // Typo fixed.
   assert (ta != NULL);

   // determine point at which to stop copying elements from old array
   // to new array

   limit = (elems < newSize) ? elems : newSize;

   for (i = 0; i < limit; i++)   // copy elements
      ta[i] = ia[i];

   // zero the enlarged part of the array, if necessary

   for ( ; i < newSize; i++)
      ta[i] = 0;

   delete [] ia;   // delete old array

   ia = ta;        // "remember" new array
   elems = newSize;
}



Thomas P. Kelliher
Sat Mar 8 11:10:15 EST 1997
Tom Kelliher