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
// 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.