More Pointers - II


The following topics are covered in this section:


Returning pointers from functions

You can return pointers (or arrays) from functions. The syntax to declare a function which will return a pointer is:

return-data-type* function-name (pararmeters);

For example:

int* create ( );

This is to declare a function called create ( ) that will return a pointer to an integer. Check out the example given below:

#include <iostream.h>
int* create( )
{

int marks[3];
int *pt=marks;
for (int i=0;i<3;i++)
{
marks[i]=80;
}
return pt;

}

int main( )
{

int *p;
p=create( );
cout<<endl<<"The marks are : "<<*(p)<<" "<<*(p+1)
<<" "<<*(p+2);
return 0;

}

The output is:

The marks are : 80 80 80

As you can see, we return ‘pt’ (which is a pointer to an integer) from the function. This is like returning an address from the function. There is nothing special about returning pointers from functions but you have to be careful with the syntax for the function header.


Run-time and Compile-time

The concept of OOPs (Object Oreinted Programming) makes emphasis on making decisions during run-time rather than at compile-time. Run-time is the time when someone runs your program. Compile-time is the time at which you compile the program.

To differentiate between run-time and compile-time let's take a real life example. Let us suppose that tonight you decide you are going to walk to office tomorrow. You decide that you will start from home at 7:30am. You have decided these two things at night itself. This is similar to compile-time. Decision is taken before the time of action.

Now in the morning, you start from home as decided at 7:30. You walk and on the way someone, your girlfriend or boyfriend, comes in a car. They stop beside you and offer to give you a lift to your office. You had initially decided to walk but suddenly change your mind. Why walk all the way to office? Maybe I could walk tomorrow. You step into the car and reach office. This was a run-time decision. You decided on the spot during the time of action.

Coming back to C++, run-time decisions provide more flexibility to adjust. As you can see, in real-life, most of us prefer to take decisions depending on the situation that arises instead of fixing a plan earlier. We take decisions on the spot instantaneously and in C++ this can be illustrated with an example. For example: in a program that makes use of an array for storing a series of numbers, you may declare an array of 20 elements. But sometimes the user may want to enter more elements. Hence you may declare an array of 200 elements. The compiler will allocate memory space for all the 200 elements during compile time itself. But your user may not always make use of the 200 elements. Sometimes it may be 10 sometimes 50. By declaring 200 elements the space allocated to the unused elements is a waste. It would be better if we could decide on the size of the array during run-time (i.e. when the user runs your program rather than fixing the size when you write and compile the program). According to the user’s preference the program could allot the required space for the array. This is a compile-time decision and this can be implemented through pointers, as we shall see later.


Dynamic Allocation

Whenever you declare variables required amount of space is allocated for them in the memory. But there are certain instances when we might not be able to predict how much space might be required when the program executes; it may depend on the user.

Suppose that you want to write a program to get the marks from the user and display the entered values. You do not know how many marks the user will enter (it could be for 2 subjects or for 8 subjects). So, you might write the following code:

# include<iostream.h>
int main ( )
{

int size,i;
cout<<"Enter the size of the array : ";
cin>>size;
int marks[size]; //WRONG
cout<<"\nEnter the marks: ";
for (i = 0; i<size; i ++)
{
        cin>>marks[i];
}
cout<<endl<<"The marks you entered are : ";
for (i = 0; i<size; i ++)
{
        cout<<endl<<marks[i];
}
return 0;
}

The program won’t compile. Why? When you reason out you might feel that it is logically correct. Think from the compiler’s point of view:

Two integers ‘i’ and ‘size’ have been declared. The compiler will allocate memory for these two integers. Next, we ask the user to input the value for ‘size’. This value is stored in memory. Next we say:

int marks[size];

Seems right, doesn’t it? We have got the value for ‘size’ from the user and now we are declaring an array called ‘marks’ which depends on what the user entered. What’s wrong with this? The problem is with memory allocation. When the compiler reads each declaration of a variable it will keep allocating memory for the variables in memory. Now the compiler cannot allocate a memory space for the array ‘marks’. Why? Because the user will enter the value of ‘size’ only when the program is run not when it is being compiled. The compiler has no idea whether to allocate space for one integer or for 10 integers (because it depends on the value of ‘size’). The compiler will give an error saying that the size of ‘marks’ is undefined. This should also illustrate the difference between compile-time and run-time. So, how to overcome this problem?

Simple. You could give the variable ‘size’ some value to start with. Replace:

cout<<"Enter the size of the array : ";
cin>>size;

with:

size=4;

Now everything should be fine. Is it so? Try it and you’ll get the same compiler error. The compiler reads and stores the value of 4 for ‘size’ but it still will not substitute that value in:

int marks[size];

The compiler assumes that ‘size’ is a variable (since it was declared like that) and since a variable’s value can change in the program, it will not compile the code. One way to correct this is by declaring the maximum size of the ‘marks’ array by saying:

int marks[4]; //program will compile

There is no need for the variable ‘size’ and the user can enter only a maximum of 4 values because that is the space allocated for the array ‘marks’.

Another way to correct the program is to declare the variable ‘size’ as a constant.

const int size=4;

Now you can use:

int marks[size];

The reason that this is valid is because the compiler makes note of the fact that ‘size’ is a constant and has been given a constant value 4. Since this value will never change in the program and space will be allocated for 4 elements in the array ‘marks’. C programmers made use of macros (instead of ‘const’):

#define SIZE 4

to define constants. When the compiler comes across the term ‘SIZE’ anywhere in the program it will simply substitute the value of 4 for ‘SIZE’.

But whatever you do, the compiler limits you to fixing the size of the array at compile-time. Can you decide the array size at run-time? Dynamic allocation comes to the rescue.

Dynamic allocation means allocating (or obtaining) and freeing memory at run-time. There are certain cases where run-time decisions are better. For example, deciding the size of an array. Similarly you can free up allotted memory in your program when you don’t need a particular array by deleting the entire array itself. The two operators used for this purpose are: ‘new’ and ‘delete’.

‘new’ is used to allocate memory while ‘delete’ is used to free the allocated memory (memory which was allocated by ‘new’). The free memory available is sometimes referred to as the heap. Memory that has been allocated by ‘new’ should be freed by using ‘delete’. If you don't use ‘delete’ then the memory becomes a waste and cannot be used by the system. This is called memory leak (i.e. when allocated memory is never returned to the heap). You could go on taking memory from the heap till it gets exhausted. This will lead to memory leak and can cause problems to your program and to other programs which attempt to take memory from the heap.

The syntax is:

data-type * name = new data-type;
delete
name ;

Beware: Both data types should be the same.

Example:

int * p = new int;
delete p;

Remember: delete p;

means that the data pointed to by ‘p’ is deleted. The pointer ‘p’ will not get deleted. The following coding is correct:

int m = 20;
int *p = new int;
*p=5;
cout<<*p<<endl; //output is 5
delete p;         //5 will be deleted but pointer ‘p’ still remains
p=&m;         //valid
cout<<*p;     //value of 20 displayed

The new and delete operators are very useful when dealing with arrays. In this case the syntax is slightly modified. Let's consider an example:

int main ( )
{ int size,i;
cout<<"Enter the size of the array : ";
cin>>size;
int *marks = new int[size];
cout<<"\nEnter the marks: ";
for (i = 0; i<size; i ++)
{
        cin>>marks[i];
}
cout<<endl<<"The marks you entered are : ";
for (i = 0; i<size; i ++)
{
        cout<<endl<<marks[i];
}
delete [ ] marks;
return 0;
}

The output would be:

Enter the size of the array : 3
Enter the marks:
50
64
53
The marks you entered are :
50
64
53

marks is an array of integer type whose size is determined by the user. If the user types 3, then the size of marks array is 3. The array has been allocated using new operator. We get the input of the array using a ‘for’ loop and then we display the entered values using another for loop.

After having used the array we free up the memory space using delete. Since we are freeing up space used by an array, we have to free up space used by each element of the array. The syntax to free up the space is:

delete [ ] name-of-array;

The point is that you have to use the square brackets when dealing with arrays (so that the compiler knows that you are referring to an array).

Can we go on taking up memory from the heap? No, the heap is a finite memory space. There is a chance of the heap getting completely exhausted. When you write programs using ‘new’ operator it is always advisable to give a provision to check whether the heap is exhausted or not. If the heap is exhausted, an exception will be returned. This will be dealt in the "Exception handling" section later.


Go back to the Contents Page


Copyright © 2004 Sethu Subramanian All rights reserved.