Memory management in C++
So far, the kind of storage we use in C++ is called STATIC or STACK
ALLOCATED or AUTOMATIC storage. This kind of storage is allocated and
deallocated based upon the block structure of the program:
int foo (int a, int b)
{
Student c;
int numbers[10];
for (int i=0; i <= 9; i++)
{
int x;
// more code
}
}
All the parameters and variables are automatically allocated when
a block is entered and de-allocated when the block is exited. This
kind of storage is allocated on the program stack as we mentioned
before.
Another issue with storage as above is that it is statically sized.
For example, numbers has size 10. Arrays are reasonable if one knows
the data size before hand. Unfortunately, this can be a problem.
What if you don't know the maximum number of elements ahead of time?
If you make the size too small, the array could prove to be inadequate
to hold all the data -- an array in C++ CANNOT be resized. It is of a
fixed size. If you make the array too large, say 10000 long just to
be safe, you could be wasting memory if you don't use all that space.
Arrays are a reasonable representation for collections of objects ONLY
if you know the maximum size of the collection ahead of time. If this
is not so, what can be done?
C++ provides a second source of memory that can be quite useful here.
This pool of storage is called the HEAP. The program HEAP is used to
create structures that can be arbitrarily large or small depending on
the run time needs of the program. For example, a collection
programmed for the heap could have 10 elements or 100 elements or 1000
elements as needed without wasting any storage. In short, heap
storage is allocated AT PROGRAM RUN TIME.
Pointers
C++ uses pointers to allocate memory on the heap. Pointers are
addresses of locations in memory. A pointer can be used to point to a
location in memory where data actually resides. A pointer declaration
also has a particular type:
// declares a POINTER TO AN INTEGER
int *p;
Means p is potentially the address of an integer. A pointer can be
made to point to a particular existing integer.
int x = 200;
p = &x; // p is made to point to the address of variable x
&x gives you the "address of variable x" in memory. p will have that
address as its value. You can "dereference a pointer" or look at the
value that it points to by using the "*" operator:
cout << "value p points to: " << *p << endl;
// will output 200 since p points to variable x, which is 200
Note that a pointer's actual value is usually not all that directly
important. Its most important role is to hold the address of the
actual data (as in the above case, it points to x, and so on). It is
illegal to simply assign values to a pointer:
p = 5; // not reasonable to directly assign to a pointer
It is ILLEGAL to dereference an unitialized pointer. Also, the
pointer value "0" is often called NULL and is assumed to point to
nothing. NULL is used to set a pointer to a legal value that points
to nothing.
"new" expression and "delete" statement
Pointers are not used simply to point to existing variables, of
course. Pointers are used to allocate new storage at run time. For
example:
p = new int;
"new" operator requests C++ to allocate new storage of a
particular type. C++ returns a pointer back to the caller
providing the location where the new storage has been
allocated.
*p = 25; // store 25 in the location to which p is pointing
The opposite of "new" is "delete". "new" allocates storage on the
heap. "delete" is used to return storage back to the heap once you
are done with that storage:
delete p; // returns the storage p points to back to heap
It is important to free up storage properly in C++ when you are using
dynamic allocation. This ensures you will have enough memory to run
your program correctly.
Memory leaks:
Sometimes, if you are not careful, it is possible that your use of
pointers results in storage belonging to your program to which you
have no pointers. This is called a "memory leak" or "garbage" --
memory that belongs to your program, but to which you have no
pointers.
int *p;
p = new int;
*p = 25;
p = new int; // what happened to the location containing 25?
// that location is a memory leak
You can also allocate an array of int:
p = new int[25];
// p is pointing to the beginning of the array
You can use pointer arithmetic to move to the next element:
*p = 2; // assigns 2 to the first element
p++;
*p = 5; // assigns 5 to the 2nd element
Note that even with the pointers, you still have the limitations of
statically sized arrays. The way to break out of that limitation is
to use linked list representation of a collection.
You can also use pointers to structure or class. If you wish to
access any of the member data or member functions through a POINTER,
you use "->" instead of ".":
class Car
{
};
Car *cp;
cp = new Car();
cp->set_seats(2);
cp->set_price(20000);
cout << "Car price: " << cp->get_price() ;
Let us say that we wanted a collection of integers of arbitrary size.
The linked list representation of a collection uses pointers with
structures to solve the problem.
Supposing I wanted a collection of integers of indefinite length:
I declare a struct as follows:
// each intlist_element has one of the items in the list
// and it POINTS to the address of the next intlist_element
// in the
// collection!! This kind of data structure has a recursive
// formulation because the struct is defined in terms of
// a pointer to itself!!
struct intlist_element
{
int data;
intlist_element *next;
};
Each element in the collection has a piece of data (the
payload), and a pointer to the next element in the
linked list.
// we can use the above declaration to build up as
// many integers as we want into a collection:
// for example, supposing we wanted to build up a collection
// 25 10 30
intlist_element collection;
// allocate the 3rd element
collection = new intlist_element();
collection->data = 30;
collection->next = NULL;
// add the 2nd element to the beginning of the list
intlist_element temp = new intlist_element();
temp->data = 10;
// manipulate the pointers so that the collection points
// to the first element
temp->next = collection;
collection = temp
temp = new intlist_element();
temp->data = 25;
temp->next = collection;
collection = temp;
Such a list is called a SINGLY LINKED LIST because there is only one
pointer in the structure that points to the next class.
Example structure: (picture)
In a single linked list representation of a collection, one usually
has access ONLY to the first element of the collection. You have to
navigate through the list to do anything useful.
To display all the values in the collection:
intlist_element temp = collection; // start at the beginning
while (temp != NULL)
{
cout << temp->data << endl;
temp = temp->next;
}
For example, to search for a value:
bool search (intlist_element collection, int val)
{
int found = false;
// start at the beginning
intlist_element temp = collection;
while (!found && temp != NULL)
if (temp->data == val) found = true;
else temp = temp->next;
if (found) return true;
return false;
}
Where is it easiest to add to a singly linked list? The answer is AT
THE BEGINNING OF THE LIST, since you have access to it. In fact, you
can see we have already demonstrated that.
Deleting from a singly linked list
To delete from a singly linked list is a little tricky. In order to
delete an element from the list, you need to locate not just that
element, but the pointer to the element before the element to delete.
You have to change its next pointer:
bool remove_element (intlist_element collection, int val)
{
int found = false;
// start at the beginning
intlist_element temp = collection, prev = NULL;
while (!found && temp != NULL)
if (temp->data == val) found = true;
else
{
prev = temp;
temp = temp->next;
}
if (found)// then we need to delete the element at temp
{
if (prev == NULL) // this is the first one!!
collection = collection->next;
else
prev->next = temp->next;
delete temp;
}
else cout << "Element does not exist: " << val << endl;
}
Make sure this works with an empty list: i.e.,
collection is NULL
Make sure that this works if you delete the first or last elements.
Make sure that this works in all other "fence post" cases
linked-list1.cpp [integer linked list]
Programming a Collection of Object using Linked Lists
The same principles that we discussed for linked lists also apply to
a linked list of objects. Many different kinds of design are possible
to build a linked list of objects. The simplest design would be to
invent a struct or class that will have the object and a pointer to
the next, as required for a singly linked list. Other than that,
there is really no difference between this design and an integer linked
list, for example.
Collection of Object using Linked Lists & NESTED CLASS
In this design, we hide the new struct or class INSIDE the Car_DB class.
This is a superior design because no other programmers are able to access
Car_DB class in any way -- meaning we can change our minds about the
design later without having to worry about other users of the class.
NESTED classes are contained entirely within another class. However, the
containing class must still respect the rights of the nested class. The
containing class has no special privileges of any kind in accessing the
nested class. It must go through the provided public member functions.
In our design, we use a PRIVATE structure -- a structure available only
to the members of the class. This has a great deal of appeal from an
encapsulation perspective because it cannot be used by non-member code,
but still affords the simplicity of struct. In other words, struct is
not a problem IF its access is restricted.
The University of Southern California does not screen or control the content on this website and thus does not guarantee the accuracy, integrity, or quality of such content. All content on this website is provided by and is the sole responsibility of the person from which such content originated, and such content does not necessarily reflect the opinions of the University administration or the Board of Trustees