Tip:
Highlight text to annotate it
X
In this lesson we will talk about pointers as return type for functions. Pointer is just
another data type. It's just that a pointer stores the address of another data type. So
it is quite possible for a function to return pointer. But we must understand the use cases
in which we may want to return a pointer from a function. So let us try to understand this.
I'll get started by writing some code.
Now I want to write a very simple C program initially. I want to write a function Add
that will take two integers as argument and sum these two numbers and return the sum.
So let's say we declare another variable c and c equals a + b and the function returns
c. Now in the main method I'll initialize two variables. Let's name these variables
x and y. Let's say x is equal to 2 and y is equal to 4 and we will have another variable
z which will be the return of this function Add and we will pass x and y as arguments
to this function. Finally, I'll print something like sum is equal to z. No prizes for guessing
the output here. Now I'll modify this code slightly, but before that I want to talk about
this concept once again that x, y and z are local variables to main function and a, b,
and c are local to Add function. What really happens when we call the Add function is that
value in this variable x of main is copied to variable a of Add. And value in y of main
is copied to b of add. And what if we name these variables in main a, b and c instead
of x, y and z. If we run this code, output will be the same. This time what we will say
is that the value of a in main is copied to a of Add and the value of b in main is copied
to b of Add.
This a in main and this a in Add - they're not the same. You can verify this by printing
something like this. I'm printing the addresses of these two 'a's in my code and as you can
see address of a in main is something like 2883032 and in Add it is 2882792. So they're
not the same. That means these variables are not the same. They're at different memory
addresses.
The names of variables are local or specific to a particular function. In our example here,
the method or the function main can be called "calling" function and the function Add can
be called "called" function in this particular call, when we're saying that c is equal to
Add and passing a and b. And this call where a and b in main are getting copied to a and
b in add, this is called a call by value.
Now what I want do is, instead of passing by value I want to pass the addresses of these
two variables. So I want to say that I want to pass the address of a and address of b
to the Add function. So the signature of Add function should be such that it should receive
the addresses. So I'll say that, okay, it takes 2 pointers to integers a and b. And
now we can access the values at these addresses by using this asterisk operator which is used
to de-reference an address. Now such a call is called call by reference. a and b are integers
local to main function and in the function Add, a and b are not integer variables. a
and b are pointer variables, pointer to integers. So their type is different. They're not int,
they're int* But at the end of the day, they're also variables which are local to the function
add. It's just that they're not integers. And now I'm using these two variables which
are pointer to integers to access these two variables a and b which are in the main method.
And to do so we use the asterisk operator and now, this code should also work.
I'll write a few more print statements inside this function Add. I have tried to print a
and I have tried to print *a and initially I was printing &a. So now &a should give us
the address of the pointer variable. a should gives us the address of a in main because
that is what this variable stores and *a should give us the value of a in main. Let us see
in the output. Now as you can see here, the address of a in main is 3537612 and address
of a in Add is something else, but the value of a in Add which is the address of a of main
is equal to 3537612. So the first and the third lines are same And using the address,
we're printing the value which is equal to 2.
Now I'll clean up some of these print statements. Coming back to our function add, we're returning
this value c. And once again in the main method, we're collecting this value c in another variable
which is c of main. Now why not do something like pass the address of this c in Add function.
So what we will do now is we will say that we want to return pointer to integer from
this function and here we will return &c. Now & when it is put in front of a variable
it gives us the address. Now of course, here we will have to collect this particular address,
so we will have to define a pointer variable. Now this would be ok. Now when we're printing,
we will have to print the value at address being pointed to by this variable. Now what
we just did is we modified this Add function to return a pointer to integer. There are
two syntax-es We can say int and then put this * sign or we can say int and put the
* sign in front of the function name Add and both these syntax-es are valid. Now this function
is returning pointer to integer. Let us run this program and see what happens.
Let me also strike off this print statement here. Okay, so the output seems to be alright.
Now have you caught some logical error with this code already? If you haven't stay with
me for sometime. Now what I want to do is I want to write another function, a simple
function that will print "hello world". So I'll name this function PrintHelloWorld and
in this function I'll write a simple print statement. Now before I print this sum, what
I'll do is I'll call this function PrintHelloWorld() And let's see what happens now. Oops - this
looks weird! Sum is not correct now. I just saw that it was coming fine in my last run
when I did not call this PrintHelloWorld. What happened? So let's try to understand
what really happened here? I'll come back to this familiar diagram of various sections
of application's memory. The memory that is allocated to a program is typically divided
into these sections. All the local variables and the information about function call executions
goes into the stack. So let us run through this code, let us simulate this code and see
what's really happening in the memory. For each function call, some part of memory from
the stack is allocated for its execution. Now we call this the stack frame of that method
or that function. When the program starts executing, first the main method is invoked.
So in the stack frame the memory will be allocated for the main function and all the local variables
of the main function will live inside this stack frame. Let's say the starting address
of this stack frame is 100 and the end address of this stack frame is 130. And we will have
3 local variables created here - a, b and ptr. a and b are integers and ptr is an integer
pointer. Let's say a is at address 100 and b is at address 112 and ptr is at address
120. I am just making these assumptions. Now when the main method will come at this line
where it is calling Add function, its execution will pause and now memory will be allocated
for the execution of Add. At any time whatever function is at the top of the stack is executing.
main method will wait for Add function to complete and return. Here I should say a is
equal to 2, b is equal to 4 and now Add comes here in the stack. Let's say Add gets memory
from 130 to 160.and Add also has 3 local variables - a, b and c. a and b are pointers to integers.
The value of a will be 100 and the value of b will be 112. Let's say their addresses are
130, 140 and 144. Once again these are just random assumptions. Now c is calculated as
*a + *b. a is pointing to this location and b is pointing to this location. *a is value
at address stored in a and *b is value at address stored in b. So this c will be 6 here.
These two values will be added. Now this add function will return the address of its local
variable c which is 144 and finish its execution. So this ptr will be 144 and now the memory
that is allocated to Add function will be de-allocated. Now this memory above address
130 can be used for other function calls. And even though this variable ptr stores the
address 144, the address of this particular block (it kind of points to this particular
block), the date here is not guaranteed because this memory has been de-allocated. Now we
come here to this PrintHelloWorld and now memory from stack will be allocated to PrintHelloWorld
above this stack frame of main method. This is main. So let's say that PrintHelloWorld
gets this memory block from address 130 to address 150. Now there is no local variable
in this PrintHelloWorld function. But still, function call execution involves storage of
some information. Now this section, from 130 to 150 is for PrintHelloWorld. I'll write
PHW - shortcut for PrintHelloWorld. And it has been over-written. So this block at 144
no more stores value 6. So when we come here at this print statement, to print the value
at this particular address, we get some garbage value. Now the obvious question would be,
why did we get the right value when we were not making the call to PrintHelloWorld. I
would say that I just got lucky. Maybe because I did not call any other function after making
a call to Add, my machine did not overwrite or erase the data at that particular memory
location. But when I made a call to PrintHelloWorld that memory got used. If you see, we have
passed the addresses of these two variables a and b of main to Add function. But that
is alright because called function always comes above the calling function in the stack.
So any time this called function is executing, calling function will be in the memory. So
if Add is executing, main is guaranteed to be in the memory. So addresses of variables
in main will be accessible to Add. But if we try to return a local variable from the
called functionback to the calling function - like if we want to return a local variable
from Add to main, when that function finishes and the control returns back to the calling
function that memory has already been de-allocated. So it is ok to pass something from bottom
to top in this call stack, or I should rather say that it is ok to pass a local variable
or address of a local variable from bottom to top in the stack but it is not ok to return
the address of a local variable from top to bottom in the call-stack. I hope this makes
sense.
So now the obvious question would be - what are the uses cases in which we may want to
return pointers from functions. Well, if we have address of some memory block in the heap
section or some memory block in the global section, then we can safely return the address
of these blocks because anything in the heap has to be explicitly de-allocated. We control
its de-allocation unlike stack. And anything which is in the global section, a global variable,
lives for the entire lifetime of the program. I can use malloc or new operator in C++ to
get some memory on heap.
So if I modify my code something like this, I will declare this c as a pointer to integer
and get some space allocated on the heap using a call to malloc. malloc is a library function
that returns pointer, but it returns pointer to an address which is on the heap. So we
get a memory block and using this pointer variable now, we can write this value - *a
+ *b - at that particular memory block. And then we can return this address c, which is
the same address that malloc returned us but we are safe now because we are returning address
of a block which is on the heap and not on the stack. And this will work now.
Now in this code, let's say this call to malloc gives us this block at address 500 in the
heap. c now is only pointing to this block and using c we have written this data here,
this value 6. And now when add finishes, the address returned by the Add function which
is address 500 is still valid. We still have the data there and it will not be de-allocated.
Anything on the heap has to be explicitly de-allocated. So while returning pointers
from functions, we need to be careful about the scope. We must be sure that the address
is not re-used to store something else or that the data is not cleared from that address.
In most cases we will be returning pointers to memory that is allocated on the heap, or
memory that is in the global section, the global variables section. In our coming lessons,
one place where we will be using pointers as function returns in our code is implementation
of linked list data structure.
So this was pointers as function returns. Thanks for watching.