Last time, we learned three different ways that we can walk an array. This time, we'll learn about dynamic memory allocation. Here's the big idea, we can dynamically allocate memory at run time as we need it. That's good because it lets us make sure that our program uses only the exact amount of memory we need, and no more. It also means that we can create the structures and allocate memory at run time even if we didn't know how much memory we needed when we wrote our code at compile time. As we've learned using Video Studio, we can't ask the user how many scores they want to enter, for example, and then allocate an array of that size because the Visual Studio compiler doesn't let us do that. Instead, we've been allocating memory for the array at run time once we knew how much memory we needed. So this control over allocating memory, we allocate it when we need it, and we allocate only how much memory we need. There is a caveat to this, and the caveat is memory leaks are bad. You may have heard that term memory leaks before. It's not entirely accurate, we don't actually have memory leaking underneath your computer, a puddle of memory. That's not how it works. Memory leaks mean that as we allocate memory, when we're done with it, we just leave it allocated and go get more. In other words, we don't release or free that memory when we're done with it, so our program uses more and more memory over time, even though it's not using lots of that memory. What that means is it's critically important that when we're done with memory, we actually free that memory. It's great to have this fine-grained control over memory, so we can ask for it when we need it. But we also have to make sure that we release it or free it when we're done because we're responsible for that fine-grained control over memory. In the code for this lecture, we're going to ask the user for a number of scores they want to enter, something between one and five. And then we'll have them fill in an array of that size. We've seen this example numerous times as we were getting valid array sizes throughout the course. And we've even seen this before because using Visual Studio, we need to use pointers to actually generate a dynamically sized array. Before we talk about the argument that we're providing to malloc, let's go look at the documentation for malloc to see how it works. So this is the C standard library documentation that we've looked at before. I will scroll down, and it's in the standard library that we do memory allocation, so I am going to click that link. And we can look at malloc, that’s what we're using right now, and it does say here that malloc takes a single argument, which is the amount of memory to allocate in bytes. So we need to pass into the malloc function how many bytes we want in the block of memory that malloc will give us. We start by saying how big is one int. Because we know this is going to be an array of ints, so each element will be an int, so this piece is how big is each element in the array. I know that on my computer with this compiler, ints are four bytes long, but it is more robust to just say sizeof(int). That way no matter what computer and what compiler you're using, this will be correct for your use of int in this particular C program. But that's only enough bytes for one element. And here we know we have count elements that the user is going to enter, so we just multiply it together. So let's say the user says they're going to enter five scores, 5 times 4 is 20, so we'll get a block of 20 bytes from malloc. It's possible for a variety of reasons that malloc fails, that it goes out and it tries to get us those 20 bytes and it can't for whatever reason. The good news is that if that happens, malloc returns null. And of course, we should just check for that here, so we'll say check for failed memory allocation. If pScores is equal to NULL, then our memory allocation failed, and we should just quit. We should return EXIT_FAILURE, Because we can't move forward. We didn't get the memory we needed for the array, so we can't have the user enter scores or anything like that. We're done for this program. If we get past there, that means we did get our 20 bytes. Remember, we only get 20 bytes if we're doing five scores that are integers, that's just the example we're using. However many bytes we asked for, if we get past this block of code, then we have those bytes in memory. So let's read in the scores. This is a standard for loop that will go from zero up to and including four if count is five. Here we prompt for a score and we add one to i because we're prompting a human. And here, we scanf. And remember, for scanf, we pass in the address of the memory location that we want to get filled up with the user's input. Now, pScores is an address, and we saw in the previous lecture that adding i to it will add the appropriate number of bytes to the memory address based on what it points to. So in this case, pScores pointing to an int. So if we have pScores plus 1, it's actually the value of pScores plus 4 bytes to give us the memory address of the next int. And you'll also notice we didn't put an address of operator here. Because pScores is an address, so we don't actually need the address of the pointer, we just need the pointer. And we saw in the previous lecture that this is one way that I can move along the memory in a for loop by adding i each time. The other thing that we saw in the previous lecture is dereferencing a more complex expression here, where we take pScores and add i to it. Because we still want to error check the scores that the user is entering. We still want to make sure that the scores are between 0 and 100, inclusive. So that stuff we saw last time, how we can dereference a memory address, is useful here as well. And here is our error loop, and it prints the error message and reads in a new score. When we're finally done, we can do another for loop. And we saw this as one of the mechanisms for walking an array with pointers in the previous lecture. And then we're almost done, but we're not totally done. It's critical that when you're done with the pointer, you do two things. You free the memory that's associated with that pointer, and the run time system remembers how large a block of memory it gave us when we called malloc to get a block of memory. So this will free not just the size that it gave us, but the actual location in memory that it gave us. So with our running example, this would free the 20 bytes of memory that it gave us for pScores. The other thing that is very good practice to do is to set the pointer to null, to indicate that that pointer is now invalid. Once we've freed this memory that pScores pointed to, pScores doesn't have semantic meaning anymore and we want to explicitly null it out. And the reason this is a really good idea is because, I know we're at the end of this particular program here. But in larger programs, if we don't do this, we might use the pScores pointer later in our program, thinking that it was still valid, and it wouldn't be. But if we haven't nulled it out, we might just access other sort of random memory locations and think everything's working because we're treating them as ints. So it's really important to do both these steps. I should run it once to show you that this all works. And because I keep saying I'm going to enter five scores, I will, but they're really low scores, and then I print them all out. So this all works fine. To recap, in this lecture, we learned how to dynamically allocate memory. And we also discussed the fact that it's critically important to free the memory when we're done with it so we don't get those nasty memory leaks. We also learned that it's good to programming practice to null out the pointer that pointed to the memory that we've just freed. So we make sure that we don't inadvertently use that pointer later, thinking it's valid when it's no longer valid.