Discussion 1H Notes (Week 8, May 20) TA: Brian Choi (schoi@cs.ucla.edu) Section Webpage: http://www.cs.ucla.edu/~schoi/cs31 Dynamic Allocation of Memory Recall that when you create an array, you must know the exact size of the array you re creating. You cannot use a variable to specify the size of an array, as follows: int len = 100; double arr[len]; // error! But there are cases when the size of an array cannot be determined a priori. In such cases, the best bet for you is to estimate the largest possible size and use it to create a long array. This does not completely solve the problem, as the actual size you need might still exceed your estimate, or the estimate might be too big that the array is not utilized 100% most of the time. For better memory utilization, you d like to use just enough memory by computing the size on-the-fly, or dynamically, and allocating exactly that much amount of memory for your array. Such a task is done as follows: int len = 100; double *arr = new double[len]; This will create an array of doubles, with the first element pointed by the pointer arr. The length of an array can be a variable this time. More precisely, the expression new double[len] creates an array dynamically, and returns the pointer to the first element of the array. We assign the returned pointer to a pointer variable, so we can keep track of the array we just created. int *func(); // Creates an array of length 10 and returns it. int *p = func(); /* Do something about this array. */ return 0; int *func() int arr[10]; return arr; // Create an array. // Return the array. Consider the above code. This code compiles and runs, but has an error. Can you tell what the error is? When func() gets called, as we have seen in the previous section, a new stack of memory is allocated for func(). All variables are created in this area, with no exception for arrays. Therefore, the line int arr[10] will create an array of 10 integers in the func() stack, with a pointer arr (also locally created) pointing to this array. Now one might think if we return the pointer, then we can use this array. This is partially correct. A pointer is memory location, and thus p will point to the location of this array. However, when func() returns and exits, this stack disappears! Suddenly the memory location p points to is invalid. (You will probably get a warning from the compiler.) See the diagram (a) on the next page. Copyright Brian Choi 2011. All Rights Reserved. Week 8, Page 1/6
Here is the correct code. (a) static case (b) dynamic case int *p = func(); /* Do something about this array. */ delete[] p; return 0; int* func() int* arr = new int[10]; // dynamic return arr; func() now dynamically creates an array. This array is not created in the stack! There is a place dedicated for dynamic data, called heap. Things in heap do not go away even after the function returns, so when func () terminates, the array still remains in the memory. What p points to is a valid location this time. We must be careful, though. Everything you create in stack will be removed from memory once you return, so you need not worry about deleting the data. The ones in heap are not going to be deleted until you delete them yourself. delete[] p; will delete the array pointed by p. So why not create everything in heap? It turns out memory management is a complicated issue. int *p = func(); p = new int[10]; // Create another array and let p point to it. What is the issue here? Do you see it? Copyright Brian Choi 2011. All Rights Reserved. Week 8, Page 2/6
Memory Leak The variables that are created dynamically do not have names. We refer to such a variable by keeping a pointer that points to the memory location at which the variable is created. What happens if we lose the pointer that points to this variable? int *p; p = new int[10]; p = new int[8]; We first create an array of size 10 and make p point to it. Then we create another array of size 8 and make p point to it, causing p to lose its previous value. Now the first array is not pointed by any pointer, and there is no way we can access it anymore. This means we cannot delete the array either. So we have a ghost that doesn t do anything yet eats up our memory. This is a problem, and we call this phenomenon memory leak. C++ compiler cannot detect this problem, thus developers must be extra cautious to avoid such a problem. If you are not going to use an array anymore, delete it. If you need to use it in the future, create another pointer and save the location. Creating a Single Variable Dynamically A variable is simply an array of size 1, so one can do: int *p = new int[1]; to create a single variable. Alternatively, one can do: int *p = new int; to create a single variable, by not specifying the size at all. To delete a single variable, you should: delete p; Delete Rule So here s a simple rule you need to remember: For each new statement, there should be one delete statement. This means you cannot use a single delete statement to delete objects that are created using more than one new statement. This also means you cannot use a delete statement to partially delete an object (like an array). An example follows: int *parr[10]; for (int i = 0; i < 10; i++) parr[i] = new int; // This is an array of pointers. // Create an integer for each element in parr. /* Do something useful with the array */ // delete[] parr; // No!!! for (int i = 0; i < 10; i++) // You need 10 delete s since you used 10 new s. delete parr[i]; Copyright Brian Choi 2011. All Rights Reserved. Week 8, Page 3/6
Pointer Practice What is the output of the following programs? int mystery(int *p) *p = 11; *(p + 1) = 12; p += 3; *(p + 1) = 15; int arr[5] = 1, 2, 3, 5, 8; mystery(arr); int mystery2(int *p) for (int i = 0; i < 5; i++) *(p + i) = i; int *arr = new int[5]; mystery2(arr); for (int i = 0; i < 5; i++) cout << arr[i] << ; for (int i = 0; i < 5; i++) cout << arr[i] << ; Question: Write a function that returns the pointer to a dynamically created array of length n, with the first n Fibonacci numbers. int *fibo(int n) // return NULL if n <= 0 int n; cout << How many Fibonacci numbers do you want to see? ; cin >> n; int *p = fibo(n); for (int i = 0; i < n; i++) cout << p[i] << ; // should print 0 1 1 2 3 5 8 13 21... if (p!= NULL) delete[] p; // Make sure we delete the array. Copyright Brian Choi 2011. All Rights Reserved. Week 8, Page 4/6
Structures Suppose I, as your TA, want to write a simple database software that will store a list of you (students) who are enrolled in my class. I want to store your name, student ID, email address, and the letter grade. For 23 students who are enrolled, I will have to create the following arrays: const int NUM_STUDENTS = 32; string names[num_students]; int ids[num_students]; string emails[num_students]; char grades[num_students]; // for names // for IDs // for email addresses // for grades This is really messy. For instance, if I were to swap two records, then I would need to perform 4 swap operations, one for each array I have here. If I can group the information that pertains to a particular student, it s going to be a lot more managable. This grouping mechanism is provided by what s called a structure. You can think of it as a new compound data type which you can customize using existing data types (like int, double, etc.). I can create a Student structure as follows. struct Student string name; int id; string email; char grade; ; // Notice that there s a semicolon that ends the definition This means each Student consists of 4 components. Each component is called the member of the structure. You can almost treat a structure as if it were just another type. You can create a Student variable by: Student st; where st is just an identifier (name). It s also possible to create an array of Students by doing: Student students[num_students]; which is equivalent to the 4-array version up there. The question now is how to access those members of a structure. You can access it by using a. (dot). Here are just a few examples: st.name = Joe Bruin ; // st s name is set to Joe Bruin students[10].id = 123456789; // the 10-th Student in students array is assigned an ID cout << st.grade << endl; // print the grade of st cout << students[0].email << endl; // print the 0-th Stduent s email address If you completely understand the 4 expressions above, you pretty much know everything you need to know about structures. To make sure, though, let s write a couple of functions that use this Student structure of ours. Copyright Brian Choi 2011. All Rights Reserved. Week 8, Page 5/6
Exercise: Define an array that takes in an array of Students and returns the GPA for the class. Assume a grade is either an A, B, C, D, or F, where the points assigned are 4, 3, 2, 1, and 0, respectively. double findgpa(student students[], int n) Exercise: Define a function printastudents that takes in an array of Students and prints the names of all students with A s. void printastudents(student students[], int n) Pointers to Structures A pointer to a structure can be defined the same way as before: Student *p; You can refer to a member of the structure pointed by p by first dereferencing it and using the dot operator. (*p).name There is nothing new about it. But here s something new: the above expression is equivalent to the following shortcut: p->name This works only if p is a pointer. We don t mean to confuse you by telling you of this shortcut. Just stick to one way that comes more natural to you. Copyright Brian Choi 2011. All Rights Reserved. Week 8, Page 6/6