Hello again! In this video, we're going to talk about pointers and memory. A pointer is a variable which
represents an address in memory, so the value of this pointer is the address.
This could be a variable on the stack or some memory that we've allocated on the heap.
If we're working at a low level, then dealing with the operating system often involves working with
pointers.
It's also quite common for devices which are attached to the computer to be memory-mapped, which means that
the operating system gives you an address in memory.
And if you write to that address, it sends data to the device.
And if you read from it, it gets data from the device.
So pointers are pretty important and they are quite central in C.
To create a pointer variable, we put an asterisk after the type name, so int star p, that declares declares p
as a pointer to int.
To initialize or assign to a pointer, we need to give us an address. So, for example, we could
take the address of some stack variable, i, so p1 is a pointer to i.
p1 will be the address of i and then to get access to the data in that memory,
we dereference the pointer. So we put an asterisk in front of the name of the pointer and that will
printout the data in there, which we expect to be one.
We can also allocate memory on the heap.
C++ has an operator called "new".
If you've used the malloc function in C, this is similar,
but not quite the same.
So new, followed by the type, will allocate memory to store that type.
So in this case, we're allocating memory to store an int. new will return the address at the start of this
memory.
So p2 is the address of this int.
This memory is going to be default initialized.
So if it's a built in type, it'll have an undefined value, which will be whatever the data in there
happened to be.
If it's a class, it'll call the default constructor for that class.
We can pass arguments to new, like that, and these arguments will be forwarded to the constructor of
the class, or used to initialize the built-in type.
So in this case, we're going to allocate memory to store an int.
And it's going to be initialized with the value 36.
Here we're using the universal initialization syntax in C++.
If you're using an older version of C++, then you have to use round brackets instead.
So let's try this out. So we have an int, i.
We are taking its address and storing that in the pointer p1. Then we are printing out the pointer
and the dereferenced pointer.
Then we allocate a couple of pointers to int.
One which is not a initialized and one which is. And actually, let's print out the values of those, so...
OK, so p1 is the address of i, so that is some hexadecimal number, which represents an address in memory
star p1
is the data in i, which is one. p2
is this memory which we didn't initialize, so the data in it could have any value.
And in fact, it has a pretty strange looking value.
With p3, we did initialize it, and the data in there has the value 36
When we allocate memory from the heap,
this will remain allocated to the program.
So the program is going to be using that memory.
It's not going to be available to anything else on the computer, and that will be the case until the
memory is released.
So if the program doesn't explicitly release it, then that memory is not available for anything else.
The operating system will restrict the amount of memory that a program uses, so the operating system
won't allow a program to hog all the memory on the computer.
If you try to allocate more than the amount that you're allowed, then the operating system may refuse
to perform the allocation.
So instead of getting a pointer to allocated memory, you'll get the value null, which is a special
kind of pointer that doesn't represent valid memory.
So failing to release memory can be a problem.
And one easy way to do that is to create what's called a memory leak.
So we've got a function here.
We have a pointer to allocated memory.
So P4 is a pointer to an int with value 42.
Then we do something with the code in here.
p4 is actually a local variable.
It's a pointer, but the actual variable itself is still a local variable.
And when we get to the end of the scope, this variable is going to be destroyed.
So the allocated memory will still exist.
But the pointer [variable] we're using to keep track of it no longer exists.
So we now no longer have any way of contacting that memory.
So that memory is just floating in space.
really! We can't use it and we can't release it.
So it's taking up memory but not doing anything useful. And that's known as a memory leak.
So here is that code.
So we have this function, which allocates memory, but doesn't release it, and it also loses track
of the pointer.
So we can't delete it in the main function.
So we're calling the function here, but we don't have any way in main to release that memory.
OK, so it seems to run normally, but in fact, this memory here is being wasted. By the time the function
returns, we can't actually get to it.
One way would be to return the pointer from this function, and then we can release it in here. Another
way would just be to actually release the memory when we finish using it. And that would be the preferred
solution.
OK, so how do we release memory?
C++ provides the delete operator.
So if we put Delete, followed by a pointer to some memory that was allocated by new, then this
will cause the memory to be released.
First of all, the destructors will be called for the objects in that memory and then the memory will
be released.
So you do get a chance to clean up the objects before they get destroyed.
Again, this variable p is still a stack object. It may represent memory that's allocated on the heap, but the
actual variable itself is on the stack.
So once the memory's been released, the variable will continue to exist until the end of that scope.
So it is actually possible for the program to access that.
And if that does happen, then that's undefined behavior.
So the program will probably crash or behave very strangely.
And the reason is that p is not pointing to memory.
Memory that is available for use.
We say that p is a "dangling pointer".
So it's very important that when you allocate memory with new, then you have a corresponding call
to delete when you finish with using the memory. To avoid memory leaks and dangling pointers.
So this is how we fix the problem with the memory leak. We call delete p4.
So this will release the memory that was allocated.
OK, so it still seems to run normally, but this time we don't have any memory leaks.
And to show you the problem with a dangling pointer.
So let's try to access this pointer after it's been released, so let's try to write to it, so we're
going to assign to the data that was in this memory.
So here we allocate the memory, we initialize it with the value 42, then we release that memory.
Then we try to write some data into this memory, even though it no longer belongs to us.
So what happens?
OK, so I think you can see there was a bit of pause after that.
If we put in a print statement, it might be a bit clearer.
OK, so it didn't actually execute this print statement, so that indicates that the program has crashed,
probably while executing this statement.
So that's something to watch out for.
And then finally, I mentioned you can have more than one object in the allocated memory.
You can actually allocate a block of memory and access it as if it were an array.
So in that case, you put square brackets after the type and then you put the number of elements inside
the square brackets.
So here we're going to allocate enough memory to hold 20 ints and then pa is going to be a pointer to the start
of this memory.
So that's going to be appointed to the first element in this array.
Then we can just go through and use the normal array syntax with square brackets.
If we're doing that, we have to use the right form of delete.
There is actually two forms of delete to match the second form of new.
And the second form of delete has a pair of square brackets between the delete and the variable.
So if we use this form, it'll tell the program that we're deleting the entire array and not just the
variable.
that happens to be at the start of the memory.
If we use the other form, then we only delete the first element.
So the first element will be destroyed, and the memory for it will be released.
But the elements after that will still be in memory, and it's possible that we may have messed up the
internal data structures in the memory management of the program.
So this could cause the program to behave strangely or crash.
So let's try this out.
So we're allocating our array of 20 ints, then we're going to go through and populate them.
Then we print out the value of each element.
And then finally, we're going to release the array. So we should expect to see the numbers from one to
20, we're allocating - sorry, from zero to 19.
OK, so we... (sorry, slight typo!)
(Only in a print statement, so it doesn't really matter!)
So we're allocating memory for the array and we populate the array.
Then we print out the array elements, then we release the memory for array, and then we finish.
Okay.
So what is some of the things that could go wrong so we could use the wrong form of delete?
Right, so it actually got to the end of the program, so we got away with that.
But we may not necessarily get away with that every time, so do be careful with that.
And then we only have 20 elements in this array, but if we try to access the 21st element, so we're
accessing some memory that doesn't belong to us.
Okay, so something different has happened, and right, we've got this strange value here.
This is not actually an element in the array. So when we defined the array we had 0, 1, 2, 3, 4,
5, 6, 7, 8, 9 up to 20 and then the memory after that is not actually part of the array.
So when we read it, we got because whatever happened to be in that memory, which converts to that
as an int.
So put that back.
And then if we try writing past the end of the array, we're going to write to some memory which doesn't
belong to us.
OK, that's definitely not right!
So we've actually got a runtime error. So we have heap corruption detected. The application wrote
to memory after end of heap buffer.
So you actually get, or at least with Visual C++ in debug mode, you actually get something that tells
you what the error was.
So... I wish compilers had been like this when I was learning C++!
So anyway.
Okay.
So that's it for this video.
I'll see you next time.
But meanwhile, keep coding!