Saturday, 18 March 2017

Pointers



 
Pointers are without a doubt one of the most important mechanisms in C. They are the means by which we implement call by reference function calls, they are closely related to arrays and strings in C, they are fundamental to utilising C's dynamic memory allocation features, and they can lead to faster and more efficient code when used correctly.

A pointer is a variable that is used to store a memory address. Most commonly the address is the location of another variable in memory.

If one variable holds the address of another then it is said to point to the second variable.

Address
Value
Variable
1000


1004
1012
ivar_ptr
1008


1012
 23
ivar
1016



In the above illustration ivar is a variable of type int with a value 23 and stored at memory location 1012. ivar_ptr is a variable of type pointer to int which has a value of 1012 and is stored at memory location 1004. Thus ivar_ptr is said to point to the variable ivar and allows us to refer indirectly to it in memory. 

NB : It should be remembered that ivar_ptr is a variable itself with a specific piece of memory associated with it, in this 32-bit case four bytes at address 1004 which is used to store an address.

Pointer Variables

Pointers like all other variables in C must be declared as such prior to use.

Syntax :           type   *ptr ;

which indicates that ptr is a pointer to a variable of type type. For example

                        int  *ptr ;

declares a pointer ptr to variables of type int.

NB : The type of the pointer variable ptr is int *. The declaration of a pointer variable normally sets aside just two or four bytes of storage for the pointer whatever it is defined to point to.
In 16-bit systems two byte pointers are termed near pointers and are used in small memory model programs where all addresses are just segment offset addresses and 16 bits in length. In larger memory model programs addresses include segment and offset addresses and are 32 bits long and thus pointers are 4 bytes in size and are termed far pointers. 
In 32-bit systems we have a flat address system where every part of memory is accessible using 32-bit pointers.

Pointer  Operators  * and &

& is a unary operator that returns the address of its operand which must be a variable.

For Example :-
int *m ;
int count=125, i ;/* m is a pointer to int, count, i are integers */
m = &count ;

The address of the variable count is placed in the pointer variable m.

The * operator is the complement of the address operator & and is normally termed the indirection operator. Like the & operator it is a unary operator and it returns the value of the variable located at the address its operand stores.

For Example :-            
           i = *m ;

assigns the value which is located at the memory location whose address is stored in m, to the integer i. So essentially in this case we have assigned the value of the variable count to the variable i. The final situation is illustrated below.




One of the most frequent causes of error when dealing with pointers is using an uninitialised pointer. Pointers should be initialised when they are declared or in an assignment statement. Like any variable if you do not specifically assign a value to a pointer variable it may contain any value. This is extremely dangerous when dealing with pointers because the pointer may point to any arbitrary location in memory, possibly to an unused location but also possibly to a memory location that is used by the operating system. If your program tries to change the value at this address it may cause the whole system to crash.  Therefore it is important to initialise all pointers before use either explicitly in your program or when defining the pointer.

A pointer may also be initialised to 0 ( zero ) or NULL which means it is pointing at nothing. This will cause a run-time error if the pointer is inadvertently used in this state. It is useful to be able to test if a pointer has a null value or not as a means of determining if it is pointing at something useful in a program.

NB : NULL is #defined in <stdio.h>.

For Example :-
           int var1, var2 ;
           int *ptr1, *ptr2 = &var2 ;
           int *ptr3 = NULL ;
           ...
           ptr1 = &var1 ;

ptr1 and ptr2 are now pointing to data locations within the program so we are free to manipulate them at will i.e. we are free to manipulate the piece of memory they point to.

Call by Reference

Recall when we wanted to swap two values using a function we were unable to actually swap the calling parameters as the call by value standard was employed. The solution to the problem is to use call by reference which is implemented in C by using pointers as is illustrated in the following example.

     #include <stdio.h>

     void swap( int *, int  * ) ;

     void main( )
     {
     int a, b ;
    
     printf( "Enter two numbers" ) ;
     scanf( " %d %d ", &a, &b ) ;
     printf( "a = %d ;  b = %d \n", a, b ) ;

     swap( &a, &b ) ;

     printf( "a = %d ;  b = %d \n", a, b ) ;
     }
     void swap ( int  *ptr1, int  *ptr2 )      
     {
     int temp ;

     temp = *ptr2 ;
     *ptr2 = *ptr1 ;
     *ptr1 = temp ;
     }

The swap() function is now written to take integer pointers as parameters and so is called in main() as
                        swap( &a, &b ) ;

where the addresses of the variables are passed and copied into the pointer variables in the parameter list of swap(). These pointers must be de-referenced to manipulate the values, and it is values in the the same memory locations as in main() we are swapping unlike the previous version of swap where we were only swapping local data values.

In our earlier call-by-value version of the program we called the function from main() as swap(a,b); and the values of these two calling arguments were copied into the formal arguments of function swap.
In our call-by-reference version above our formal arguments are pointers to int and it is the addresses contained in these pointers, (i.e. the pointer values), that are copied here into the formal arguments of the function. However when we de-reference these pointers we are accessing the values in the main() function as their addresses do not change.

Pointers and Arrays

There is a very close relationship between pointer and array notation in C. As we have seen already the name of an array ( or string ) is actually the address in memory of the array and so it is essentially a constant pointer.

For Example :-
char str[80], *ptr ;

ptr =  str ;/* causes ptr to point to start of string str  */
ptr = &str[0] ; /* this performs the same as above */

It is illegal however to do the following

str = ptr ;     /* illegal */

as str is a constant pointer and so its value i.e. the address it holds cannot be changed.

Instead of using the normal method of accessing array elements using an index we can
use pointers in much the same way to access them as follows.

char str[80], *ptr , ch;

ptr = str ;          // position the pointer appropriately

*ptr = 'a' ;         // access first element i.e. str[0] 
ch = *( ptr + 1 )  ; // access second element i.e. str[1]

Thus    *( array + index )   is equivalent to  array[index]. 

Note that the parentheses are necessary above as the precedence of * is higher than that of +. The expression
           ch = *ptr + 1 ;

for example says to access the character pointed to by ptr ( str[0] in above example with value ‘a’) and to add the value 1 to it. This causes the ASCII value of ‘a’ to be incremented by 1 so that the value assigned to the variable ch is ‘b’.            

In fact so close is the relationship between the two forms that we can do the following

int x[10], *ptr ;

ptr = x ;
ptr[4] = 10 ;   /* accesses element 5 of array by indexing a pointer */

Pointer Arithmetic

Pointer variables can be manipulated in certain limited ways. Many of the manipulations are most useful when dealing with arrays which are stored in contiguous memory locations. Knowing the layout of memory enables us to traverse it using a pointer and not get completely lost.

·      Assignment
int count, *p1, *p2 ;

p1 = &count ;        // assign the address of a variable directly
p2 = p1 ;       // assign the value of another pointer variable, an address

·      Addition / Subtraction

The value a pointer holds is just the address of a variable in memory, which is normally a four byte entity. It is possible to modify this address by integer addition and subtraction if necessary.  Consider the following we assume a 32-bit system and hence 32-bit integers.

int *ptr ;


Address
Value

int array[3] = { 100, 101, 102 } ;

ptr
   1000
2008

ptr = array ;

   
   




array[0]
   2008
100



array[1]
   2012
101



array[2]
   2016
102


We now have the pointer variable ptr pointing at the start of array which is stored at memory location 2008 in our illustration. Since we know that element array[1] is stored at address 2012 directly after element array[0] we could perform the following to access its value using the pointer.

                        ptr += 1 ;

This surprisingly will cause ptr to hold the value 1012 which is the address of array[1], so we can access the value of element array[1]. The reason for this is that ptr is defined to be a pointer to type int, which are four bytes in size on a 32-bit system. When we add 1 to ptr what we want to happen is to point to the next integer in memory. Since an integer requires four bytes of storage the compiler increments ptr by 4. Likewise a pointer to type char would be incremented by 1, a pointer to float by 4, etc.

Similarly we can carry out integer subtraction to move the pointer backwards in memory.

     ptr = ptr - 1 ;
           ptr -= 10 ;

The shorthand operators ++ and -- can also be used with pointers. In our continuing example with integers the statement  ptr++ ; will cause the address in ptr to be incremented by 4 and so point to the next integer in memory and similarly ptr-- ; will cause the address in ptr to be decremented by 4 and point to the previous integer in memory.

NB : Two pointer variables may not be added together ( it does not make any logical sense ).

           char *p1, *p2 ;
           p1 = p1 + p2 ;  /* illegal operation */

Two pointers may however be subtracted as follows.

           int *p1, *p2, array[3], count ;
           p1 = array ;
           p2 = &array[2] ;

           count = p2 - p1 ;    /* legal */
The result of such an operation is not however a pointer, it is the number of elements of the base type of the pointer that lie between the two pointers in memory.

·      Comparisons

We can compare pointers using the relational operators ==, <, and  >  to establish whether two pointers point to the same location, to a lower location in memory, or to a higher location in memory. These operations are again used in conjunction with arrays when dealing with sorting algorithms etc.


For Example :- Writing our own version of the puts() standard library function.

1. Using array notation
           
void puts( const char s[ ] ) /* const keyword makes string
                                contents read only */
{
int i ;

for ( i = 0; s[i] ; i++ )
     putchar( s[i] ) ;
putchar( '\n' ) ;
}

2. Using pointer notation

void puts( const char *s ) // char *const s would make pointer unalterable
{
 while ( *s )
     putchar( *s++ ) ;
 putchar( '\n' ) ;
}

As you can see by comparing the two versions above the second version using pointers is a much simpler version of the function. No extra variables are required and it is more efficient as we will see because of its use of pointer indirection.

For Example :- Palindrome program using pointers.

            #include  <stdio.h>
            int palin( char * ) ;      /* Function to determine if array is a palindrome. returns 1 if                                                              it is a palindrome, 0 otherwise */
            void main( )
            {
            char str[30], c ;

            puts( "Enter test string" ) ;
            gets( str ) ;

            if ( palin( str ) )
                        printf( "%s is a palindrome\n", str ) ;
            else
                        printf( "%s is not a palindrome\n") ;
            }

            int palin ( char *str )
            {
            char *ptr ;

            ptr = str ;
            while ( *ptr  )
                        ptr++ ;              /* get length of string  i.e. increment ptr while *ptr != '\0' */
            ptr-- ;                           /* move back one from '\0'                              */

            while ( str < ptr )
                        if ( *str++  !=  *ptr-- )
                                    return 0 ;                                  /* return value 0 if not a palindrome */

            return 1 ;                      /* otherwise it is a palindrome */
            }

           

Strings and pointers


C's standard library string handling functions use pointers to manipulate the strings. For example the prototype for the strcmp() function found in <string.h> is

                        int strcmp( const char *string1, const char *string2 ) ;

where const is a C keyword which locks the variable it is associated with and prevents any inadvertent changes to it within the function.

Strings can be initialised using pointer or array notation as follows

                        char *str = "Hello\n" ;
                        char string[] = "Hello\n" ;

in both cases the compiler allocates just sufficient storage for both strings.

Arrays of Pointers

It is possible to declare arrays of pointers in C the same as any other 'type'. For example

                        int *x[10] ;

declares an array of ten integer pointers.

To make one of the pointers point to a variable one might do the following.

                        x[ 2 ]  =  &var ;

To access the value pointed to by x[ 2 ] we would do the following

                        *x[ 2 ]

which simply de-references the pointer x[ 2 ] using the * operator.

Passing this array to a function can be done by treating it  the same as a normal array which happens to be an array of elements of type int *.

For Example : -
                        void display( int *q[ ], int size )
                        {
                        int t ;
                        for ( t=0; t < size; t++ )
                                    printf( "%d ", *q[t] ) ;
                        }

Note that q is actually a pointer to an array of pointers as we will see later on with multiple indirection.

A common use of pointer arrays is to hold arrays of strings.

For  Example :-  A function to print  error messages.

                        void serror( int num )
                        {
                        static char *err[] = {
                                    "Cannot Open File\n",
                                    "Read Error\n",
                                    "Write Error\n" } ;

                        puts( err[num] );
                        }

Note that using an array of pointers to char initialised as above conserves space as no blank filling characters are required as would be if we used

            char err[3][30] = {
                        ... } ;

Command Line Arguments

Command line arguments allow us to pass information into the program as it is run. For example the simple operating system command type uses command line arguments as follows

            c:>type text.dat

where the name of the file to be printed is taken into the type program and the contents of the file then printed out.

In C there are two in-built arguments to the main() function commonly called  argc and argv which are used to process command line arguments.

                        void main( int argc,  char *argv[ ] )
                        {
                        ...
                        }

argc is used to hold the total number of arguments used on the command line which is always at least one because the program name is considered the first command line argument.
argv is a pointer to an array of  pointers to strings where each element in argv points to a command line argument. For example argv[0] points to the first string, the program name.

For Example :- Program to print a name ( saved in name.c ) using command line arguments.

            #include <stdio.h>
            void main( int argc, char *argv[ ] )
            {
            if ( argc != 2 )
                        {
                        puts( "Missing parameter.  Usage : name yourname" ) ;
                        exit( 1 );
                        }
            printf( "Hello %s", argv[1] ) ;
             }

To run the program one might type

            c:\>name tom


For Example :- Program to count down from a given value, the countdown being displayed if the argument  "display" is given.

            #include <stdio.h>
            #include <stdlib.h>
            #include <ctype.h>
            #include <string.h>

            void main( int argc, char *argv[ ] )
            {
            int disp, count ;

            if ( argc < 2 )
                        {
                        puts("Missing Arguments Usage : progname count [display]" );
                        exit(1) ;
                        }
           
            if ( argc  > 2 && !strcmp( argv[2], "display" ) )
                        disp = 1 ;
            else
                        disp = 0 ;

            for ( count = atoi( argv[1] ) ; count ; count-- )
                        if ( disp )
                                    printf( "%d\n", count ) ;
            printf( “done\n” ) ;
            }

NB : C has a broad range of functions to convert strings into the standard data types and vice versa. For example atoi() converts a string to an integer above - remember all command line arguments are just character strings.

Dynamic Memory Allocation

This is the means by which a program can obtain and release memory at run-time. This is very important in the case of programs which use large data items e.g. databases which may need to allocate variable amounts of memory or which might have finished with a particular data block and want to release the memory used to store it for other uses.

The functions malloc() and free() form the core of C's dynamic memory allocation and are prototyped in <malloc.h>. malloc() allocates memory from the heap i.e. unused memory while available and free() releases memory back to the heap.

The following is the prototype for the malloc() function

                        void * malloc( size_t num_bytes ) ;

malloc() allocates  num_bytes  bytes of storage and returns a pointer to type void to the block of memory if successful, which can be cast to whatever type is required. If malloc() is unable to allocate the requested amount of memory it returns a NULL pointer.

For example  to allocate memory for 100 characters we might do the following

            #include <malloc.h>

            void main()
            {
            char *p ;
           
            if (  !( p = malloc( sizeof( char  ) * 100  ) )
                        {
                        puts( "Out of memory" ) ;
                        exit(1) ;
                        }
            }

The return type void * is automatically cast to the type of the lvalue type but to make it more explicit we would do the following

            if (  !( (char * )p = malloc( sizeof( char  ) * 100  ) )
                        {
                        puts( "Out of memory" ) ;
                        exit(1) ;
                        }

To free the block of memory allocated we do the following

            free ( p ) ;

Note :- There are a number of memory allocation functions included in the standard library including calloc( ), _fmalloc( ) etc. Care must be taken to ensure that memory allocated with a particular allocation function is released with its appropriate deallocation function, e.g. memory allocated with malloc() is freed only with free() .

Multiple Indirection -- Pointers to Pointers

It is possible in C to have a pointer point to another pointer that points to a target value. This is termed multiple indirection in this case double indirection. Multiple indirection can be carried out to whatever extent is desired but can get convoluted if carried to extremes.

In the normal situation, single indirection, a pointer variable would hold the address in memory of an appropriate variable, which could then be accessed indirectly by de-referencing the pointer using the * operator.

In the case of double indirection, we have the situation where a variable may be pointed to by a pointer as with single indirection, but that this pointer may also be pointed to by another pointer.         So we have the situation where we must de-reference this latter pointer twice to actually access the variable we are interested in. De-referencing the pointer to a pointer once gives us a normal singly indirected pointer, de-referencing the pointer to a pointer secondly allows us to access the actual data variable. The situation is depicted in the diagram below.





To declare a pointer to a pointer we include another indirection operator

                        float  * * ptr ;

which in this case defines a pointer to a pointer to type float.

The following illustrates some valid operations using double indirection.

                        int x = 10, *p, **q ;    

                        p = &x ;
                        q = &p ;

                        **q = 20 ;        // de-reference twice to access value
                        p = *q ;                        // de-reference q once to get a pointer to int
                       
int array1[] = { 1,2,3,4,5,6 ,7 ,8,9,10} ;
int array2[] = {10,20,30,40,50} ;        
int *pointers[2] ;                                  // an array of pointers to type int
int **ptr ;                                             // a doubly indirected pointer

ptr = pointers ;             // initialise pointer to array of pointers
*ptr++ = array1 ;          // now we simply de-reference the pointer to a pointer
*ptr = array2 ;              // once and move it on like any pointer

**ptr = 100 ;                // ptr is pointing at pointers[1] which in turn is pointing
                                     // at array2 so array2[0] is assigned 100
For Example :- Allocation and initialisation of an m x n matrix using double indirection

What we require here is to allocate an n x n matrix as a collection of discrete rows rather than just as one block of memory. This format has advantages over a single block allocation in certain situations. The structure we end up with is illustrated below.




#include <stdio.h>
#include <malloc.h>

void main( void )
{
double **ptr_rows, **user_ptr, *elem_ptr ;
int m, n, i, j ;

printf( “\n\nEnter the number of rows and columns required (m, n) : “ ) ;
scanf( “%d, %d”, &m, &n ) ;
_flushall() ;

ptr_rows = ( double **) malloc( m * sizeof ( double * ) ) ;                 // space for row pointers

user_ptr = ptr_rows ;
for ( i = 0; i < m ; i++ )                                                             // and then row elements
            {
            *user_ptr = (double *) malloc( n * sizeof( double ) ) ;

            elem_ptr = *user_ptr ;
            for ( j = 0; j < n ; j++ )
                        *elem_ptr++ = 1.0 ;

            user_ptr++ ;                             // move onto next row pointer
            }

                        // after use we need to clean up in reverse order
user_ptr = ptr_rows ;

for ( i = 0; i < n; i++ )
            free( *user_ptr ++ ) ;                // free a row and move onto next

free( ptr_rows ) ;                                  // free pointers to rows

}

6.10 Pointers to Functions


A function even though not a variable still has a physical address in memory and this address may be assigned to a pointer. When a function is called it essentially causes an execution jump in the program to the location in memory where the instructions contained in the function are stored so it is possible to call a function using a pointer to a function.

The address of a function is obtained by just using the function name without any parentheses, parameters or return type in much the same way as the name of an array is the address of the array.

A pointer to a function is declared as follows

Syntax :           ret_type  ( * fptr ) ( parameter list ) ;           

where fptr  is declared to be a pointer to a function which takes parameters of the form indicated in the parameter list and returns a value of type ret_type.

The parentheses around * fptr are required because without them the declaration

            ret_type  * fptr( parameter list ) ;

just declares a function fptr which returns a pointer to type ret_type !

To assign a function to a pointer we might simply do the following

            int (*fptr)( ) ;
           
            fptr = getchar ; /* standard library function */


To call the function using a pointer we can do either of the following

            ch = (*fptr)( ) ;            
            ch = fptr( ) ;

Example :- Program to compare two strings using a comparison function passed as a parameter.

            #include <stdio.h>
            #include <string.h>
            void check( char *a, char *b, int ( * cmp ) ( ) );

            void main( )
            {
            char s1[80], s2[80] ;
            int (*p)( ) ;

            p = strcmp ;

            gets(s1) ;
            gets( s2 );

            check( s1, s2, p ) ;
            }
            void check ( char *a, char *b, int (* cmp)( ) )
            {
            if ( ! cmp( a, b ) )
                        puts( "equal" ) ;
            else
                        puts( "not equal") ;
            }

Note that even though we do not specify parameters to the function pointer in the prototype or declarator of the function we must specify them when actually calling the function.

Note also that instead of using an explicitly declared pointer variable to call the required function in main() we could make the call as follows

            check( s1, s2, strcmp ) ;

where we essentially pass a constant pointer to strcmp( ).

For Example : Program that may check for either numeric or alphabetic equality.

            #include <stdio.h>
            #include <ctype.h>
            #include <stdlib.h>
            #include <string.h>

            void check( char *a, char *b, int ( * cmp ) ( ) );
            int numcmp( char *, char * ) ;

void main( )
{
char s1[80], s2[80] ;

gets(s1) ;
gets( s2 );

if ( isalpha( *s1 )                                 // should have a more rigorous test here
            check( s1, s2, strcmp ) ;
else
            check( s1, s2, numcmp ) ;
}
void check ( char *a, char *b, int (* cmp)( ) )
{
if ( ! cmp( a, b ) )
            puts( "equal" ) ;
else
            puts( "not equal") ;
}

int numcmp( char *a, char *b )
{
if ( atoi( a ) == atoi( b ) )
            return 0 ;
else
            return 1 ;
}

6.11 Efficiency Considerations


When used correctly pointers can lead to more efficient code in situations where sequential operations on contiguous blocks of memory are required.

For example when accessing each element of an array sequentially. The inefficient way to do this is

                        for ( k = 0; k < 100; k++ )
                                    array[ k ] = 0.0 ;

When done this way the compiler has to index into the array for each iteration of the loop. This involves reading the current value of the index, k, multiplying this by the sizeof( double ) and using this value as an offset from the start of the array.

The exact same thing occurs if we use a pointer incorrectly as follows
                       
                        ptr = array ;
                        for ( k = 0; k < 100; k++ )
                                    *( ptr + k ) = 0.0 ;

whereas the most efficient solution is of course to do the following where the pointer itself is moved by the appropriate amount.

                        ptr = array ;
                        for ( k = 0; k < 100; k++ )
                                    *ptr++ = 0.0 ;

In this case we just incur the addition of sizeof( double ) onto the address contained in the pointer variable for each iteration.

Exercises

1. Write your own functions to carry out some of the standard string manipulation tasks included in the standard library, e.g.. strcpy(), strcat(), strlen(), strstr( ).

Use the same prototypes as the standard functions but use slightly different function names in order to be able to test the functions in parallel with the originals.

2.  There is a logical bug in the following program.  Compile run and debug the program and see if
you can find it.

            #include <stdio.h>
            #include <string.h>

            void main()
            {
            char  *p1 , s[80] ;

            p1 = s ;
            do
                        {
                        gets( s ) ;

                        while( *           p1 )
                                    printf("%d",*p1++ ) ;

                        } while(strcmp(s, "done") ) ;
            }

3.   The following program section contains a logical error. Run the program and explain what happens.

            #include <stdio.h>
            #include <string.h>

            void main()
            {
            char *p1 = "abc", *p2 = "pacific sea" ;

            printf("%s %s %s\n",p1, p2, strcat(p1, p2) );
            }

4.   Use a simple bubble sort algorithm to sort an array of characters into alphabetical order.

The bubble sort algorithm is as follows. Starting at the beginning of the array examine the first pair of adjacent elements and exchange their values if not in the correct order.  Next move on by one element to the next pair of values - the pairs will overlap with the previous pair so that the second element of the first pair becomes the first element of the second pair. Examine the ordering of this pair as before. After the first pass of the bubble sort over the complete array the last array element is in the correct position, after the second pass the second last is in position, etc. Thus after each pass the unsorted portion of the array contains one less element. This procedure should be repeated until either a complete pass in which no swaps are required is made or in the worst case until one less than the total number of elements number of passes have been made.

5.   Write a program which sets up an array of pointers to char which are initialised to days of the week say. Write and test a function which takes an integer parameter to print out the corresponding
day .
 e.g..              1  ---  Sunday,  2  ---  Monday,  etc.

The function should print out an invalid message if an incorrect number is passed to it.

6.  Write and test a program to sort a list of words into alphabetical order. The standard library string handling functions may be used.  A maximum number of words to be sorted and a maximum length of each word should be specified in the interest of memory requirements e.g. limit the number of words to 5 and the length of each word to 10 .

7.   Write and test a function which can perform unsigned integer addition with up to 50 digits of
precision maintained.

For Example :-

             9123412341234123412341234123412341234
+           1123412341234123412341234123412341234
____________________________________________

           10246824682468246824682468246824682468
The program should read in the two operands and present  the result of the operation as well as the two operands.

Note : There is no C data type which can maintain up to 50 digits of precision.

8.   Write a simple function e.g. to add two numbers and define a pointer to this function to call the
function.

9.  Write a program which will calculate and print out the values of various trigonometric operations from 0 rad to 2*PI rad at intervals of PI/4.

The trigonometric operations supported  ( sin, cos, tan , etc.) should be entered as command line
parameters. e.g.

                        A:\>trigprog  sin

A single generic function should be used to compute and print out the values of the operation requested  taking a pointer to the appropriate standard library function as a parameter.

Prototypes for the C  standard library maths functions are included in <math.h>.

10.  Write a program which will sort an array of integers using the standard library qsort() function.  The qsort() function requires a pointer to a user supplied  comparison function as a parameter which might have the following  prototype

                        int comp( int *p1, int  *p2 ) ;

A return value of 0 indicates equality, a return value of 1 indicates element *p1 > element *p2, and
a return value of -1 indicates element *p1 <  element *p2.


11. Programming Assignment : Base Conversion Program

Your task in this assignment is to write a C program which converts an unsigned short in a specified number base, base1, to another number base, base2.

The number to be converted is to be entered into the program as an ASCII string and the two bases, base1 and base2, as integers in base 10. The program must then convert the number in base1 to base2 and output the results for the user, continuing until an EOF is encountered ( see later ).

For Example :- the following input should cause an output statement like that presented below.

            12  10  2
            12 in base 10 is 1100 in base 2

Only base values in the range 2 -- 10 inclusive are to be allowed in the program, so you should check

1.   that base1 and base2 are appropriate base values and
2.   that the number string entered does not contain any invalid digits, i.e. digits outside the range for base1 (or indeed any alphabetic characters, but it may skip leading white-space characters )

before attempting any conversions. In either case your program should ignore the values completely and write out an appropriate error message to the output and then continue with the program.

To convert a number in base1 to base2 it may be simpler to carry out the operation in two steps as follows
1.   Convert number string in base1 to number in base 10 and check that it is in range for unsigned short.
2.   Convert base 10 number to base2 number string.

To carry out these two tasks you should provide two functions with the following prototypes

unsigned short convert_10( char *ptr, unsigned short base_b ) ;
char *convert_b( unsigned short num_10, unsigned short base_b );

The convert_10( ) function takes a pointer to an ASCII string which contains a number in base_b and should return this number as a base 10 unsigned integer.

For Example :-             23 in base 4 = 2 x 41 + 3 x 40 = 8 + 3 = 11 in base 10.

Your function should check that the value does not exceed the value for an unsigned short and return an error value if it does. ( Suggestion : Make 0 -- 65534 valid values and use 65535 as an error return.)

The function convert_b( ) takes a valid integer in base 10 as its first parameter and converts it to a number in base_b, which you must represent as an ASCII string.

Your function should return a pointer to a string containing the number in base_b, which should be tailored down so that the bare minimum of space is used ( i.e. dynamically allocated once the number has been converted ). This pointer may be freed in the calling function once it has finished with the number string in question.


Your program should be written so that it accepts a number of optional command line arguments as follows
                        progname  [source_file]  [ destination_file]

If no command line arguments are present the program should read its input from stdin, the keyboard and write the results to stdout, the monitor.

If one command line argument is present it means that the program is to take its input from the file specified if possible where it is arranged as follows

                                                            12  10  2
                                                            24  5  7

and write the results to the standard output.

Lastly if two arguments are given the program is to read its input from the specified file and write the results to the specified destination file.

In all cases your program is to continue reading input from the input device in question until an EOF is encountered ( or until a conversion error occurs ). Note that EOF may be produced at the keyboard by Ctrl+Z.

Note : You may not use any of the following family of functions in your program for any reason unless you implement your own versions of them.

                        atoi( ), atol( ), itoa( ), ltoa( ), etc.


12. Programming Assignment : Tabulation Program.

Your main task in this assignment is to write and test a function

int *tabulate( int *data, int numvals ) ;

This function takes as its parameters a pointer to an array of integers and the number of integers in this array and should tabulate the values in the array storing the results in a second array and returning it as the functions return value.

·      Your main program is to first read in a user specified number of integers from the keyboard and store them in a dynamically allocated array and then call the tabulate function.

·      The tabulate function must first determine the range of the data being passed to it i.e. the maximum and minimum values it contains and then allocate a block of memory covering this range. For example if the data set was { 6, 10, 6, 8, 7 } then min = 6 and max = 10 so we need to allocate a block of ( max - min + 1 )  = 5 integers to contain all values in the range. The first element in the array is for the value 6, the second for 7, etc.

·      We also need to store the max and min range values which tells us how many data values are in the array. To do this we modify the above representation so that we allocate two extra elements to store the range. The first element in the array is deemed to be for the minimum, the second for the maximum and the remainder for the data as before.

·      This new block of data is next initialised to zero i.e. no instances of any of the values in the range are present in the data. The data would look as follows with our above example.

min
max
# 6’s
# 7’s
# 8’s
# 9’s
# 10’s
6
10
0
0
0
0
0

·      The function should now look at the data set and increment the count associated with all the elements in it appropriately. The data set should now look like the following and will be returned to the main program where the results will be displayed.

min
max
# 6’s
# 7’s
# 8’s
# 9’s
# 10’s
6
10
2
1
1
0
1

·      Your program is also required to accept one extra command line argument. If this can be opened as a file the results of the tabulation are to be printed to it otherwise they are to be printed to stdout.


13. Programming Assignment : Polynomial Parser

The basic requirement of this assignment is to write a C program which can resolve a simple polynomial into a vector of its components and to make use of this form to implement differentiation of the equation.

The equation parser should be implemented as a function which is passed an ASCII character string which is used to represent a polynomial of degree n which has the general form

                       

where x is the dependant variable, an are the coefficients of the polynomial (which may be limited to signed integer values).

The character string representation may be of any degree, n, and the components may be in any random order and may even be repeated. Use the ‘^’ character to represent to the power of.  The character string may also include white space characters for readability, namely the space character, which do not convey any information but which nonetheless must be processed being part of the string. Some examples of valid equations are given below where '_' represents the space character.

            1 + x + 2x^2 + 5x^4
            2x^4 + 3x -12
            -6 + 2x^3 - 2x + 4 - x
            -4 x^-2 + x^-1+ 2x^3
            x_^_2_+_2_x_^_3

The equation parser must resolve the polynomial into a vector representation, ie. it must establish a value for the coefficient of each component of the polynomial. This information or vector should be stored in a single dimension integer array where the index into the array represents a specific component of the vector. Vector[0] represents the coefficient of x^0, Vector[1] represents the coefficient of x^1, and so on up to the degree of the polynomial.

The size of the vector array should be just sufficient to store the required vector, memory being allocated dynamically once the degree of the polynomial has been established.  For example a polynomial of degree 2 would require an array of three elements to store all the component coefficients. 

For testing purposes your program should first of all take in a polynomial in the form of a character string as input, resolve this into a vector using the parser function, and then pass this vector to a function which should differentiate the vector ( or equation ) and store the result in a different vector of appropriate dimension.

The results of each operation should be displayed in tabular form as illustrated in the following example where the equation input was    5x^3 + 3x^2 -  x + 4.

Coefficients :   x^0      x^1      x^2      x^3
Equation :        4          -1         3          5
Equation' :       -1         6          15       

The results of the operation should also be stored to a user specified text file in the same tabular form as above. In the case where the file specified already exists and  has a number of valid entries, the new entry to be added should be separated by a blank line from the others for readability.



No comments:

Post a Comment