====== Pointers 4 ====== Dynamic memory, memory management, pointers as return types.((Portions adapted from: Gaddis, Tony. "Pointers." In //Starting Out with C++: From Control Structures through Objects//. 8th ed. Boston: Pearson, 2015. 495-546.)) ===== 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 '' reserves a block of memory large enough to hold the specified %%%% 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. /** Dynamically allocate and use two doubles. */ #include 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: /** Dynamically allocate and use an array. */ #include 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: /** Example showing local variable lifetime. */ #include 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. /** Example showing lifetime of dynamically allocated storage and a * a small memory leak. */ #include 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? /** Example of a sizable memory leak. */ #include 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: /** Example showing deallocation. */ #include 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;'' // This program totals and averages the sales figures for any // number of days. The figures are stored in a dynamically // allocated array. #include #include 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: /** Returning a pointer from a function. */ #include 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. // This program demonstrates a function that returns // a pointer. #include #include // For rand and srand #include // 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 ''. One smart pointer is the ''unique_ptr'': ''**unique_ptr<**//type_pointed_to//**>** //pointer_name//**(** //expression_to_allocate_memory// **)**'' // This program demonstrates a unique_ptr. #include #include using namespace std; int main() { // Define a unique_ptr smart pointer, pointing // to a dynamically allocated int. unique_ptr 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: // This program demonstrates a unique_ptr pointing // to a dynamically allocated array of integers. #include #include 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 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.