Professional Documents
Culture Documents
Advanced C Programming
Advanced C Programming
Tech students)
Pointers
Why we need pointers?
The correct understanding and use of pointers is crucial to successful C
programming. There are several reasons for this: First, pointers provide the means by
which functions can modify their calling arguments. Second, pointers support dynamic
allocation. Third, pointers can improve the efficiency of certain routines. Finally, pointers
provide support for dynamic data structures, such as binary trees and linked lists.
Memory addresses are global to all functions, whereas local variable names are
meaningful only within the functions in which they are declared. A function can pass the
address of a local variable to another function, and the second functions can use this
address to access the contents of the first functions local variable.
What are pointers?
A pointer is a variable that holds a memory address. This address is the location of
another object (typically another variable) in memory. For example, if one variable
contains the address of another variable, the first variable is said to be point to the second.
To make this explanation clearer, were going to look some examples of source code, and
draw pictures to show what is happening in memory. To keep things consistent, were
going to develop a pictorial notation that will be used throughout this discussion. Lets
start off with an example.
This is a very simple program. The diagram on the right shows what is happening in
memory. The rectangular boxes indicate memory locations. The value inside a box
indicates the value in that memory location. The value under a box is the memory address
of that box. In this case, we have three boxes one for each of the three variables in
Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT
Look first at Diagram 2a. This shows the state of the program memory just after line 5
has been executed. The variable num was assigned the value 57 at line 4. At line 5, the
address-of operator is used to retrieve the address of the variable num, and store this
value in pNum. Since num resides at memory location 1000 (as is shown under the box),
the value 1000 is put in pNum. pNum is now pointing to num. This has been indicated
on the diagram with an arrow. Note how the variable pNum was declared in line 3. The
asterisk (*) there is not the dereference operator. In this context, it is used to tell the C
compiler that pNum is not an int, but is a pointer to an int (an int*). Do not think of the
arrow as a pointer. Its better to think of the box pNum as the pointer. A pointer is just
like any other variable; the arrow is just there to help you find the box it is pointing to.
Now look at Diagram 2b. This shows the program memory when the program is finished.
The dereference operator is used at line 6. *pNum tells the compiler to take the value
inside pNum (which is 1000), and go get the box with that value underneath it. That box
happens to be the one labeled num in this case. The rest of line 6 tells the compiler to
put the value 23 inside that box. If you run this program, the number 23 will be printed.
The value of num has been changed.
The pointer operators
There are two pointer operators: * and &.
& is a unary operator that returns the memory address of its operand. You can think of &
as returning the address of. The second pointer operation, *, is the complement of &. It
is a unary operator that returns the value located at the address that follows. You can
think of * as value at address.
Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT
p2 = p1;
Case 2: Converting one type of pointer to another type, without using void * pointer:
The conversion must be performed by using an explicit cast. However, the conversion
of one type of pointer into another type may create undefined behavior. For example,
p = (int *) &x;
Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT
p1--;
p1 = p1 + 10;
p1 = p1 10;
Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT
Initializing Pointers
Consider the following statements:
int *p;
//Declares a pointer
*p = 70;
//Declares a pointer pointing to the, first cell (having address 70), piece of
Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT
Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT
By this we are actually declaring a pointer of data type data_type, with name arr_name.
In C89, the size of an array must be specified using a constant expression(In C99 we can
declare array of variable size). In C, all arrays have 0 as the index of their first element.
Within index box [], we can use any expression while accessing. For example: [i+j],
[i++], etc.
Strings:
String is an array of characters with its last meaningful character followed by NULL
character. In C language the group of characters, digits, and symbols enclosed within
quotation marks are called as string. There is no string type in C; strings are merely a sort
of abbreviated notation for specifying character arrays. The control string of printf(), that
we include with in double quotes, is just a string. For a character array to be treated as a
string, its last meaningful character must be followed by the ASCII value 0, known as the
Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT
Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT
As a pointer:
(int *x)
ii)
As a sized array:
(int x[5])
iii)
As an un-sized array:
(int x[])
Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT
Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT
And then we could print out our array either using the array notation or by dereferencing
our pointer. The following code illustrates this:
----------- Sample Program ----------------------------------#include <stdio.h>
int my_array[] = {1,23,17,4,-5,100};
int *ptr;
int main(void)
{
int i;
ptr = &my_array[0];
printf("\n\n");
for (i = 0; i < 6; i++)
{
printf("my_array[%d] = %d ",i,my_array[i]); /*<-- A */
printf("ptr + %d = %d\n",i, *(ptr + i));
/*<-- B */
}
return 0;
}
Compile and run the above program and carefully note lines A and B and that the
program prints out the same values in either case. Also observe how we dereferenced our
pointer in line B, i.e. we first added i to it and then dereferenced the new pointer. Change
line B to read:
printf("ptr + %d = %d\n",i, *ptr++);
and run it again... then change it to:
printf("ptr + %d = %d\n",i, *(++ptr));
Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT
i = 2;
Here, while i is a variable and then occupies space in the data portion of memory, 2 is a
constant and, as such, instead of setting aside memory in the data segment, it is imbedded
directly in the code segment of memory. That is, while writing something like k = i; tells
the compiler to create code which at run time will look at memory location &i to
determine the value to be moved to k, code created by i = 2; simply puts the 2 in the code
Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT
void *vptr;
Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT
char *pB;
pB = strB;
/* point pB at string B */
putchar('\n');
Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT
puts(strB);
return 0;
}
In the above we start out by defining two character arrays of 80 characters each. Since
these are globally defined, they are initialized to all '\0's first. Then, strA has the first 42
characters initialized to the string in quotes. Now, moving into the code, we declare two
character pointers and show the string on the screen. We then "point" the pointer pA at
strA. That is, by means of the assignment statement we copy the address of strA[0] into
our variable pA. We now use puts() to show that which is pointed to by pA on the
screen. Consider here that the function prototype for puts() is:
int puts(const char *s);
For the moment, ignore the const. The parameter passed to puts() is a pointer, that is the
value of a pointer (since all parameters in C are passed by value), and the value of a
pointer is the address to which it points, or, simply, an address. Thus when we write
puts(strA); as we have seen, we are passing the address of strA[0]. Similarly, when we
write puts(pA); we are passing the same address, since we have set pA = strA;
Given that, follow the code down to the while() statement on line A. Line A states:
While the character pointed to by pA (i.e. *pA) is not a nul character (i.e. the terminating
'\0'), do the following: Line B states: copy the character pointed to by pA to the space
pointed to by pB, then increment pA so it points to the next character and pB so it points
to the next space. When we have copied the last character, pA now points to the
terminating nul character and the loop ends. However, we have not copied the nul
character. And, by definition a string in C must be nul terminated. So, we add the nul
character with line C.
It is very educational to run this program with your debugger while watching strA, strB,
pA and pB and single stepping through the program. It is even more educational if
instead of simply defining strB[] as has been done above, initialize it also with something
like:
strB[80] = "12345678901234567890123456789012345678901234567890"
Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT
Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT
Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT
Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT
strcat();
strchr();
Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT
};
Once the new structure data-type has been defined one or more variables can be declared
to be of that type. Now, struct <struct_name> will start functioning as new-data type.
struct <struct_name> <structure_variables_1>, , <structure_variable_n> ;
Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT
Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT
/* last name */
Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT
/* age */
float rate;
};
Let's say we have a bunch of these structures in a disk file and we want to read each one
out and print out the first and last name of each one so that we can have a list of the
people in our files. The remaining information will not be printed out. We will want to do
this printing with a function call and pass to that function a pointer to the structure at
hand. For demonstration purposes I will use only one structure for now. But realize the
goal is the writing of the function, not the reading of the file which, presumably, we
know how to do.
For review, recall that we can access structure members with the dot operator as in:
--------------- program -----------------#include <stdio.h>
#include <string.h>
struct tag {
char lname[20];
/* last name */
char fname[20];
/* first name */
int age;
/* age */
float rate;
};
struct tag my_struct;
int main(void)
{
strcpy(my_struct.lname,"Jensen");
strcpy(my_struct.fname,"Ted");
printf("\n%s ",my_struct.fname);
printf("%s\n",my_struct.lname);
return 0;
}
Now, this particular structure is rather small compared to many used in C programs. To
the above we might want to add:
Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT
Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT
char lname[20];
/* last name */
char fname[20];
/* first name */
int age;
/* age */
float rate;
};
struct tag my_struct;
/* a pointer to a structure */
/* point the pointer to my_struct */
strcpy(my_struct.lname,"Jensen");
strcpy(my_struct.fname,"Ted");
printf("\n%s ",my_struct.fname);
printf("%s\n",my_struct.lname);
my_struct.age = 63;
show_name(st_ptr);
return 0;
}
void show_name(struct tag *p)
{
Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT
Now the BALANCE has been defined, it can be used in another typedef.
typedef BALANCE OVERDRAFT;
FILES
We frequently use files for storing information which can be processed by our programs. In
order to store information permanently and retrieve it, we need to use files. Files are not only
used for data. Our programs are also stored in files. The editor which you use to enter your
program and save, simply manipulates files for you. In order to use files we have to learn about
File I/O i.e. how to write information to a file and how to read information from a file. We will
see that file I/O is almost identical to the terminal I/O that we have being using so far. The
Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT
Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT
Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT
Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT
In this code fragment, we keep reading filenames from the user until a valid existing filename is
entered.
Exercise:
Modify the above code fragment to allow the user 3 chances to enter a valid
filename. If a valid file name is not entered after 3 chances, terminate the program.
RULE: Always check when opening files, that fopen succeeds in opening the files
appropriately.
Obeying this simple rule will save you much heartache.
Example 1: Write a program to count the number of lines and characters in a file.
Note: Each line of input from a file or keyboard will be terminated by the newline character \n.
Thus by counting newlines we know how many lines there are in our input.
/*count.c : Count characters in a file*/
#include <stdio.h>
void main()
/* Prompt user for file and count number of characters and lines in it*/
{
FILE *fopen(), *fp;
int c , nc, nlines;
char filename[40] ;
nlines = 0 ;
nc = 0;
printf(Enter file name: );
gets( filename );
fp = fopen( filename, r );
if ( fp == NULL )
{
printf(Cannot open %s for reading \n, filename );
Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT
nc++ ;
c = getc ( fp );
}
fclose( fp );
if ( nc != 0 )
{
printf(There are %d characters in %s \n, nc, filename );
printf(There are %d lines \n, nlines );
}
else
printf(File: %s is empty \n, filename );
}
Example 2: Write a program to display file contents 20 lines at a time. The program pauses
after displaying 20 lines until the user presses either Q to quit or Return to display the next 20
lines. (The Unix operating system has a command called more to do this ) As in previous
programs, we read the filename from user and open it appropriately. We then process the file:
read character from file
while not end of file and not finished do
begin
display character
if character is newline then
linecount = linecount + 1;
if linecount == 20 then
begin
Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT
{
printf(Cannot open %s for reading \n, filename );
exit();
/* terminate program */
}
linecount = 1 ;
reply[0] = \0 ;
c = getc( fp ) ;
/* Display character */
if ( c == \n )
linecount = linecount+ 1 ;
if ( linecount == 20 )
{
linecount = 1 ;
Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT
The string reply will contain the users response. The first character of this will be reply[0]. We
check if this is q or Q. The brackets [] in printf are used to distinguish the programs message
from the file contents.
Errors and End of the file
Error in opening the file
To detect any error in opening the file, such as a write-protected or full-disk, before
our program attempts to write to it, use the following code:
FILE *fp; //After this, fp will point to some Garbage Address
clrscr();
//Important to include
fp = fopen(Test, r);
if (fp == NULL)
{
printf(Error in opening the file\n);
getch();
exit(0);
}
Error in closing the file
A return value of zero signifies a successful close operation (thats strange, as usually
we associate non-successful operation with zero). The function returns EOF if an error
occurs. Generally, fclose() will fail only when a disk has been prematurely removed
from the drive or there is no more space on the disk.
if (fclose(fp) != 0)
{
printf(\nError in closing the file);
Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT
Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT
Also note that after writing anything to a file, we can not use the condition ! feof ( fp )
until we close that file using fclose ( fp ) and open it again.
w+ (w + reading)
a+
(a + reading)
If you are interested in writing, it behaves just as w. If you are interested in reading,
all the contents of the file (both already existing and just written) can be read out.
Thus, pointer may be set to point to first character of the file.
Reading and Writing in files
The putc() function for input
Its syntax is given below:
putc(ch, fp);
putc(character_constant, fp);
Working:
The putc() function writes characters to a file that was previously opened for writing
using fopen() function. New line will not be printed automatically.
Return value:If putc() operation is successful, it returns the character written. Otherwise,
it returns EOF.
The getc() function for input
Its syntax is given below:
ch = getc(fp);
Working:
The getc() function reads characters from a file opened in read mode by fopen().
Return Value: The getc() function returns an EOF when the end of the file has been
reached. However, getc() also returns EOF if an error occurs.
Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT
Working:
The fgets () function reads a string from the specified stream until either a newline
character is read or a maximum of length 1 characters have been read. If a newline is
read, it will be part of the string (unlike the gets() function). The resultant string will
always be null terminated. A newline character is always kept in str by fgets () after
length 1 characters or already read newline character.
Cases 1:
Characters before newline (in specifies stream) length 1
The string str :
M U N I S \n \0
Case 2:
Characters before newline = length 2
The string str :
S I T A \n \n \0
Case 3:
Characters before newline = length 3
The string str :
R A M \n \n \0
Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT
Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT
Macro Name
Beginning of file
SEEK_SET
Current position
SEEK_CUR
End of file
SEEK_END
You can use fseek() to seek in multiples of any type of data by simply multiplying the
size of the data by the number of the item you want to reach. For example:
fseek(fp, -(9*sizeof(struct addr)), SEEK_CUR);
You can determine current location of a file using ftell(). Its prototype is
long int ftell(FILE *fp);
It returns the location of the current position of the file associated with fp. If a failure
occurs, it returns -1.
Linked List
Why Linked Lists?
Linked lists and arrays are similar since they both store collections of data. The
terminology is that arrays and linked lists store "elements" on behalf of "client" code. The
specific type of element is not important since essentially the same structure works to
store elements of any type. One way to think about linked lists is to look at how arrays
work and think about alternate approaches.
What Linked Lists Look Like
An array allocates memory for all its elements lumped together as one block of memory.
In contrast, a linked list allocates space for each element separately in its own block of
memory called a "linked list element" or "node". The list gets is overall structure by using
pointers to connect all its nodes together like the links in a chain. Each node contains two
fields: a "data" field to store whatever element type the list holds for its client, and a
"next" field which is a pointer that is used to link one node to the next node. Each node is
Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT
The beginning of the linked list is stored in a "head" pointer which points to the first
node. The first node contains a pointer to the second node. The second node contains a
pointer to the third node, ... and so on. The last node in the list has its .next field set to
NULL to mark the end of the list. Code can access any node in the list by starting at the
head and following the .next pointers. Operations towards the front of the list are fast
while operations which access node farther down the list takes longer the further they are
from the front. This "linear" cost to access a node is fundamentally more costly then the
constant time [ ] access provided by arrays. In this respect, linked lists are definitely less
efficient than arrays. Implement the linked list using arrays. There are so many types of
linked list available. It is up to the users interest to learn more.
Stack: Basic ideas
A Stack is a data structure with which you can perform the following operations (their
conventional names are in bold):
Access the data item on the top (other data items are hidden from view), Top
Remove and discard the data item on the top, thus exposing the next item which
now becomes the top item, Pop.
The order in which data in added ("pushed"), accessed and removed ("popped") is LIFO
(last in, first out). This is different from a queue, in which it's FIFO (first in, first out).
Both stacks and queues are ubiquitous in RL ("real life").
Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT
// the index of the last added (pushed) item a stack is initially empty,
void main()
{
int n,ch,op=1;
void push(int);
int pop();
clrscr();
while(op==1)
{
printf("Stack operations....1. Push 2. Pop");
scanf("%d",&ch);
switch(ch)
{
case 1: printf("Give the element to be pushed...");
scanf("%d",&n);
push(n);
break;
case 2: n = pop();
printf("Element %d is poped ",n);
break;
default:printf("Please give a vaid choice");
break;
}
printf("Press 1 to continue and other digits to quit...");
scanf("%d",&op);
}
}
Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT
int pop(){
if( index == -1 ){
printf( "Warning: IntStack underflow, nothing to pop!\n");
return;
}
return arr[index--];
}
Queues
The queue is another data structure. A physical analogy for a queue is a line at a bank.
When you go to the bank, customers go to the rear (end) of the line and customers come
off of the line (i.e., are serviced) from the front of the line.
Aside: In fact, other English-speaking countries use this term for a line, e.g., they might
say "Queue up!" rather than "Get in a line!"
Like a stack, a queue usually holds things of the same type. We usually draw queues
horizontally. Here's a queue of characters with 3 elements:
queue
------------|a|b|c|
------------^
|
front
^
|
rear
Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT
Removes an object from the front of the queue and produces that object.
IsEmpty
^
|
rear
^
|
rear
Now, ch = Delete(queue)...
queue
ch
Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT
^
|
front
rear
You'll notice that the queue enforces a certain order to the use of its contents. Is
the ordering of the queue Last thing In is the First thing Out (LIFO) or First
Thing In is the First thing Out (FIFO)?
Answer: Queues produce FIFO order. Remember that stacks produce LIFO
order.
Implementing a queue with an array:
Since a queue usually holds a bunch of items with the same type, we could implement a
queue with an array. Let's simplify our array implementation of a queue by using an array
of a fixed size, MAX_QUEUE_SIZE.
What other pieces of data would you need (besides an array) to implement a queue in this
way?
One of the things we'll need to keep track of is the number of elements in the queue, i.e.,
not all the elements in the array may be holding elements of the queue at any given time.
So far, the pieces of data we need for our array implementation of the queue are:
an array
a count
Will these pieces be sufficient? Let's look at an example to find out...We'll start with a
queue with 3 elements:
queue (made up of 'contents' and 'count')
----------------- ----|a|b|c| | |3|
----------------- ----0 1 2 3
count
contents
where a is at the front and c is at the rear. Now, we enter a new element with:
Enter(queue, 'd')...
queue (made up of 'contents' and 'count')
----------------- -----
Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT
count
contents
count
ch
contents
Hmmm, we have a problem because the front of the queue is no longer at array position
0. One solution would be to move all the elements down one, giving:
queue (made up of 'contents' and 'count')
----------------- ----|b|c|d| | |3|
----------------- ----0 1 2 3
count
contents
We reject that solution though because it is too expensive to move everything down
every time we remove an element.
Instead, can we use an additional piece of information to keep track of the front?
Answer: Yes! We can use the index of the element at the front, giving:
queue (made up of 'contents', 'front' and 'count')
----------------- ----- ----| |b|c|d| |1| |3|
----------------- ----- ----0 1 2 3
front count
Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT
front count
contents
Finally, how would it look like after: ch = Delete(queue)?
queue (made up of 'contents', 'front' and 'count')
----------------- ----- ----- ----|e| |c|d| |2| |3| |b|
----------------- ----- ----- ----0 1 2 3
front count
ch
Contents.
Implement the queue using arrays similar to that of stacks. If you are interested, try to do
a case study on types of queues.
Try to implement the list, stack and queues in alternate ways also.
------------------------------------------------------------------------------------------------------------
Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT