More Pointers - VI

The following topics are covered in this section:

Passing multi-dimensional arrays to functions:

There is another important point to discuss. But before we go into that we’ll need to find out how to pass multi-dimensional arrays to functions. Let’s write a function which has a 2-D array as its argument.

void disp(char str)
{
for (int i=0;i<5;i++)
{
cout<<endl<<str[i];
}

int main( )
{
char names={
{"Tintin"},
{"Frank"},
{"Watson"},
{"Poirot"},
{"Snowy"},
};
disp(names);
return 0;

The program is simple; it just displays the 5 names, each on a separate line. Take note of the function header. We’ve specified it as:

void disp(char str)

Do we really need to specify the dimensions? When passing 1-D arrays to functions we discovered that since arrays are anyway passed by reference, there is no necessity to specify the dimension (for a 1-D array). But if you try:

void disp(char str[ ][ ])

in the above example you’ll get some compiler errors. If you try:

void disp(char str[ ])

still we get errors but if we try:

void disp(char str[ ])

you’ll find that the program works perfectly! So, you might be wondering why this 2nd dimension is so important in a 2-D array. To answer this, we’ll have to look at things from the compiler’s viewpoint (and we’ll also need to recollect how arrays are stored in memory).

Let’s modify our disp( ) function such that it would display only the 3rd letter of every name. The function becomes:

void disp(char str)
{
for (int i=0;i<5;i++)
{
cout<<endl<<str[i];
}

Forget everything and just concentrate on:

cout<<endl<<str[i];

What does the computer need to do when it reads this instruction? This is what the computer will think:

1.)    The function disp has been passed a 2-D character array and it is called ‘str’.

2.)    The value of ‘i’ is 0 for the first iteration.

3.) The user wants me to display str. So I need to calculate the address of s    str. I’ll use the formula:
The address of str = base address of the 2-D array + offset

where
and
offset = (number of columns * element’s row number) + element’s col. Number

Hence the equation becomes:

Offset = (7 * 0) + 2 = 2

But this value is in terms of the number of elements I need to move forward. Since a character occupies 1 byte, I have to move forward by 2 bytes from the base address to access the 3rd character that the user wants me to display. Let’s assume that we use the function header:

void disp(char str[ ])

in our example program (let’s try to find out why the compiler produces a error in this case). We need to examine the way in which the computer accesses an individual element of a 2-D array. The combined equation is:

Address Of str = &str + [  { ( 0 * 7 ) + 2 }  * 1 byte ]

If you take a closer look you’ll realize that the computer can obtain the values of 0 and 2 in the equation from str (the two numbers are present in the index itself). It already knows the address of str (this is what would be passed to the function) and it also knows that the multiplicative factor is 1 byte (because in our function header we specify that ‘str’ is a 2-D character array). The only quantity in the equation that the computer doesn’t have is the number 7. This corresponds to the number of columns present in the 2-D array and without this information the computer cannot calculate the address of str (for that matter it cannot calculate any address; try some other cases and you’ll understand). This is the reason why we need to specify the second dimension in our function header. The first dimension (5 in our case) is not needed in any of the calculations and hence this can be ignored. So,

void disp(char str[ ])

will cause an error while

void disp(char str[ ])

is acceptable.

If you use a function declaration then you need to specify the 2nd dimension in the function declaration also. You can either specify:

void disp(char str[ ]);

or you can also specify:

void disp(char[ ]);

since in a function declaration you don’t need to mention the name of the parameter.

The concept can be extended to higher dimension arrays. The same logic applies in this case also (i.e. you can forget about the first dimension but you should specify all the other dimensions in your function header).

Functions with variable arguments:

There may be occasions when you want to create a function which can accept varying number of arguments (so far we’ve only dealt with functions having a fixed number of arguments). Programmers in C have surely used such functions frequently in their programs. The most famous example is: printf( ). This function is used to display data on the console and it is designed to handle multiple arguments (i.e. you can pass 1 argument or even 10 arguments to the function and it will faithfully perform the operation).

Let’s say that we want to create a function called sum( ) which should add up all the arguments and return the result to the caller. The caller can pass any number of integers to sum( ) and we should design the function to handle this.

#include <stdarg.h>
#include <iostream.h>

/* This function can accept varying number of integer arguments.
It will sum the arguments and return the result.
*/

int sum(int a, ...)
{
va_list args;
va_start(args,a);
int result=a;

for(; ;)
{
int temp=va_arg(args,int);
if (temp==0)     //can also check for NULL
{
break;
}
else
{
result+=temp;
}
}

va_end(args);
return result;

int main( )
{
cout<<"\nThe sum is : "<<sum(1,2,3,4,5,NULL);
return 0;
}

The result will be 15, as expected.

How it works?

The only thing we need to understand is how the function sum( ) works. So let’s break down the program and analyze it line-by-line.

int sum(int a, ...)

Some compilers also accept:

int sum(int a ...)

This is the way we tell the compiler that sum( ) will take multiple arguments. In such functions there should be one fixed argument. In our case that argument is

int a

The three ellipsis (…) indicate that varying number of arguments will follow. Some compilers will also accept:

va_list args;
va_start(args,a);
int result=a;

The header file stdarg.h defines a structure called va_list. The variable ‘args’ can be used to iterate through the list of unknown arguments which have been passed to the function. You can consider it as a kind of pointer.

va_start( ) is a macro defined in the header file and this will initialize args to point to the first unknown argument. The second argument of va_start tells the compiler about the size of the data type (in our case it will be sizeof(a) which is the size of an integer). This is needed to because va_start will offset the address by the size of an integer (thus taking us to the first unknown argument). We’ll discuss in detail about macros in a later chapter. Macros are used by the preprocessor and not the compiler.

Next we have an infinite for loop.

int temp=va_arg(args,int);

va_arg will return the first unknown argument (pointed to by args) and moves to the next unknown argument. We keep summing up the integers till we reach the end of the list (NULL or zero).

va_end(args);

This will set args to NULL (i.e. you won’t be able to use the variable ‘args’ after this statement).

Remember: va_start, va_arg and va_end are all macros.

Beware: You’ll have to be really careful when passing char, short and float to such functions because these data types are promoted to int and double when used by va_arg( ).

Note: If you are really keen to see how these macros are implemented then you can take a look at them in the stdarg.h file (you can just search your PC to find out where the file resides).

Recap:

·        Every byte in memory has a memory address (location).

·        Instructions and data are stored in memory locations and are accessed by the computer using their address location.

·        Pointers are variables used to store memory addresses.

·        * is the ‘dereferencing’ operator (it is also the indirection operator depending on the situation) and & is the ‘address of’ operator.

·        Pointer arithmetic depends on the data type pointed to by the pointer.

·        Pointers are used in arrays, for dynamic memory allocation and for passing by reference to functions.

·        An array name actually refers to the address of the first array element.

·        Dynamic memory allocation operators are ‘new’ and ‘delete’. They can be used to determine the size of an array at run-time.

·        By default, arguments to functions are passed by value (i.e. the function will operate on a copy of the arguments passed to it). But arrays are passed-by-reference to functions.

·        Only when arguments are passed by reference will the function operate directly on the arguments.

·        Reference variables are used in C++ to implement pass-by-reference.

·        Structures are usually passed using pointers rather than passing the entire structure to the function.

·        Multiple indirection refers to the concept of ‘pointers to pointers’.

Go back to the Contents Page