~~SLIDESHOW~~ ~~NOTOC~~ ====== Pointers 4 slides ====== 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.))\\ 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 '' * reserves a block of memory to hold the specified %%%% * returns the base address of that block. * optional parenthesis around '''' 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 ===== /** 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. * Warning: If there is not enough memory available to allocate the desired block, BadThings™ will happen. /** 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; } ===== 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. /** 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; // 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. /** 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 = 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: /** 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; } ===== 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: /** 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. } ===== Deallocating arrays ===== Use square brackets to deallocate dynamically allocated arrays: ''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 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: /** 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; } ===== 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 ===== // 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. * You need to ''#include ''. * 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. // 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; } ===== Example ===== Dynamically allocating a large block of managed memory. // 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 ===== * Other smart pointers are ''weak_ptr'' and ''shared_ptr''. * Not covered here.