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