In this video, we'll learn how to return a pointer from a function.
In c++, functions can return pointers, which is a super powerful feature.
In order to do this, we need to provide the type of the pointer
in the function definition and in the function prototype.
All this means is that the function will return an address
of whatever type we specified in the function declaration.
Let's see an example.
In this function, we're returning a pointer to an integer.
In this case, it'll be the value of the parameter that
points to the largest integer.
It's okay to return a pointer to data that's being passed into a function
since we know that data exists.
So in this case, we compare the two integers that the two pointers are
pointing to and return the pointer that points to the largest integer.
Notice that we're not comparing the pointers themselves.
We're comparing what they're pointing to by de-referencing them.
Pretty straightforward, but how do you call this function?
I'll show you in the next slide.
Here we have a simple main that calls the largest int
function that we just wrote.
I have two integers a and b, b is the largest with 200.
I call largest int and pass in the addresses of a and b.
This is correct since it expects pointers to integers.
The function returns a pointer, which I assign to a variable named
largest pointer which is declared as a pointer to an integer.
So now that variable, largest pointer, points to either a or to b
depending on which one was largest.
In this case, it's b.
So we de-reference the pointer, and it displays 200.
Let's see an example where we allocate memory dynamically inside a function
and return the address of that memory.
Returning dynamically allocated memory from a function is a very
common use case for returning a pointer from a function.
In this example, we created a function called create array, and it expects
an unsigned integer that contains the size of the array to create and
a default parameter called init value and that's the value we're going to
initialize all the array elements to.
The function returns a pointer to an integer.
Now this represents the address of that first integer
that we dynamically created.
You can see the code is pretty standard.
After we create the storage, we use a for loop
and initialize all the array elements to init value.
Notice that I'm using pointer offset notation within the loop, but I could
have used pointer subscript notation.
Finally, we return new storage which is the address of that first
integer in the newly created array.
So how do we use this function? I'll show you in the next slide.
In this sample code we, call create array and we pass in 100 and 20.
That means I want 100 integers allocated dynamically on
the heap and I want to initialize all of them to 20.
So this function allocates the space dynamically on the heap for those 100
integers, initializes them all to 20 and returns the address of that first
integer, which I assigned to my array.
Now I can use my array, however, I like.
But remember, since the storage is on the heap, you need to release it.
So you have to remember to use delete with the square brackets
on that pointer variable.
Now let's see what not to do when returning pointers from a function.
Here I have two functions.
The first function is called don't do this and the second
function is called or this.
They both return pointers to integers.
The first function returns the address of size.
This is a real problem.
Size is a local variable to the function.
This will compile just fine since the address of size is
the address of an integer and that's what the function returns.
But what's the problem?
Well, you can see it's a huge problem.
We're returning the address of a local variable in the function.
The variable's on the stack and the function just terminated, so this
variable is now past its lifetime.
The next time the function is called or any function is called, the stack
area will be reused and the pointer will now be pointing into that
new functions activation record.
If you overwrite the data it's pointing to, you could trash the
stack pointers, static links, all kinds of important information
on the activation record.
Hopefully, the program crashes or you get a really strange
error that you can fix.
But if the program changes data that isn't currently being used,
then the program may appear to work correctly for a while.
These types of errors or bugs are very difficult to find.
The second function has the same problem except that
it's assigned a pointer to size variable and returns it.
In both cases, the address that's being returned is a
stack variable or a function local variable, very bad idea.
Okay, that completes this video.
You'll get a chance to work with functions that return pointers
to dynamically allocated storage in the challenge exercise
at the end of this section.
But first, let's head over to the IDE, and we'll go over some
of these functions in live code.
Okay, so I'm in the IDE.
I'm in the section 12 workspace in the ReturnPointer project.
And I'd like to walk through this example when we created that create
array function, that dynamically allocates some storage on the heap.
So let's walk through this example.
Right here, we're in main.
And you'll notice that main is activated when the program
begins, and we've got three local variables in main.
We've got my array, which currently is just nulled out.
We have size, which contains garbage right now.
And we have init value, which is zeroed out at the moment.
Okay, so what do we do?
We ask the user how many integers would you like to allocate,
and we read that into size.
Let's say that they want to allocate 10 integers.
So we'll put a 10 in here.
And then what value would would you like to initialize to?
How about we initialize them all to number to the two.
So now we've got those variables.
And what we do now is we call create array and we pass in
the size and the initial value.
So we're here now, here's the function create array.
Notice create array has one, two, three local variables, right.
It's got size, and let me call create array here.
So I'm calling create array with the size, which is 10 and
the initial value which is 2.
If i didn't supply an initial value, it would use 0.
There's the default initializer here.
But in this case, size is 10 initial value.
And notice again, I'm using the same variable names.
I'm doing that on purpose don't let that confuse you, 2.
And then we've got new storage right here which is a pointer to an integer.
That's where we're going to allocate the memory from when we call
new right here on the next line.
And right now that's null.
Okay, so now new storage equals new int size.
How many integers do I want to allocate dynamically?
However, many integers were passed in?
In this case, 10.
So 10 integers will be allocated dynamically on
the heap, not on the stack.
This is the stack.
The heap is a different part of memory.
It's way up here, let's say, and we're going to allocate 10
integers, 0 through 9, on the heap.
Let's say that this is address 5000.
That's the address of that very first integer right here.
What's being returned and assigned to new storage is
that first integers address.
So we're putting a 5000 in here.
There's our pointer.
Now this is real important.
We've got a pointer on the stack.
That's pointing to storage on the heap.
This is exactly what we want.
We've got to be careful, not to lose that pointer.
If we lose that pointer, we've got a memory leak.
Okay, great.
So now we have that memory allocated, and we want to initialize it.
Remember right now that all that is garbage data right in here.
So what do we do?
We just use a simple for loop.
We're going from 0 less than size by 1.
And we're initializing every element in this array to whatever was being
passed in an initial value right here.
In this case, we passed in a 2.
So we're going to store 2 everywhere.
And here, I'm using pointer offset notation.
I could have just said new storage sub-I and use pointer array
notation or pointer subscript notation, either one is fine.
So in this case, we're putting a 2 in all of these guys.
All 10 of them will have 2s in them.
And now, this is the piece that's really important.
We return new storage.
But what's new storage?
It's 5000.
We return that value 5000 and notice in main, we're
assigning it to my array.
So we're putting a 5000 in here.
So now this pointer is pointing to the same place.
That's really important because this function is almost done.
So when this function gets popped off the stack, we're going to
lose this pointer, right here.
And we don't want to lose that pointer, so that's why
we assign it to my array.
So now we've got a copy of that pointer that we can use.
Okay, good.
So at this point, the function is now done.
We start cleaning it up.
So all this is gone now, right.
So this pointer here is now gone.
We lose all this.
And we're back to main.
Now notice main has that pointer.
So we didn't lose our memory out on the heap.
That's really important to understand.
Okay, so now, let's display the data.
So I'm calling this function right now, display, and I'm passing in, in
this case, my array, my array is 5000.
That's the value of that pointer and the size, which was 10.
Okay, So display now gets activated on the stack, and it's got 1, right
here, variable, 1 local variable another local variable and it's
got another variable right here I.
Okay, I'm not going to worry about I.
You know all about I, it's just a looping variable.
So it's going to have this variable array and it's going to have a size.
Okay, so 5000 gets copied to array, 10 gets copied to size.
Notice that this is now pointing to that same place, but it's constant.
We've got a constant pointer, and we're pointing to a constant array.
So we can't mess with that array either by accident or intentionally.
And all we're doing is we're just looping through there and displaying
out every element in the array.
Notice that in this case I'm using pointer subscript notation here.
Whereas, over here, I use pointer offset notation, same exact thing.
So this function now goes through there, displays
all those 2s 10 of them.
When we're done, same as before, we basically start unwinding.
So this is done.
After we do all the displaying, we're gone here.
This is gone, and we're back to main.
Now I've got the storage allocated on the heap.
I'm done with it.
I need to release it or free it up, and that's what's
happening right here at line 34.
Delete square brackets and the name of the pointer.
You've got to make sure that the value of that pointer is
something that was newed.
Okay, you just can't point to like a stack reference and then delete that.
So in this case, this will now be freed up.
And that memory will be ready to be used again.
So let's clear this up and give it a run.
So we'll build and run here.
So let's say we want to allocate 10 integers, and we
want to set all of them to 5.
There you see, we're displaying all 10 integers, each one
is set to 5, pretty easy.
Let's run this again.
And suppose we want to allocate a 1000 integers this time, and we want to set
each one of them to, I don't know, 88.
We run it, there we get a 1000 integers, all set to 98.
What's important to understand here is that it's all on the
heap, which is pretty cool.
Okay, so that finishes off this video.
I hope this makes a lot of sense.
If you have any questions, please let me know.