C Programming AllClasses-Outline-198-233
C Programming AllClasses-Outline-198-233
memory, use it for a while, but then reach a point where they don't need that
particular piece any more. Because memory is not inexhaustible, it's a good
idea to deallocate (that is, release or free) memory you're no longer using.
Dynamically allocated memory is deallocated with the free function. If p
contains a pointer previously returned by malloc, you can call
free(p);
which will “give the memory back'' to the stock of memory (sometimes called
the “arena'' or “pool'') from which malloc requests are satisfied. Calling free is
sort of the ultimate in recycling: it costs you almost nothing, and the memory
you give back is immediately usable by other parts of your program.
(Theoretically, it may even be usable by other programs.)
(Freeing unused memory is a good idea, but it's not mandatory. When your
program exits, any memory which it has allocated but not freed should be
automatically released. If your computer were to somehow “lose'' memory just
because your program forgot to free it, that would indicate a problem or
deficiency in your operating system.)
Naturally, once you've freed some memory you must remember not to use it
any more. After calling
free(p);
it is probably the case that p still points at the same memory. However, since
we've given it back, it's now “available,'' and a later call to malloc might give
that memory to some other part of your program. If the variable p is a global
variable or will otherwise stick around for a while, one good way to record the
fact that it's not to be used any more would be to set it to a null pointer:
free(p);
p = NULL;
Now we don't even have the pointer to the freed memory any more, and (as
long as we check to see that p is non-NULL before using it), we won't misuse
any memory via the pointer p.
When thinking about malloc, free, and dynamically-allocated memory in
general, remember again the distinction between a pointer and what it points
to. If you call malloc to allocate some memory, and store the pointer which
malloc gives you in a local pointer variable, what happens when the function
containing the local pointer variable returns? If the local pointer variable has
Manipal University Jaipur Page No.: 198
Programming in C Unit 1
automatic duration (which is the default, unless the variable is declared static),
it will disappear when the function returns. But for the pointer variable to
disappear says nothing about the memory pointed to! That memory still exists
and, as far as malloc and free are concerned, is still allocated. The only thing
that has disappeared is the pointer variable you had which pointed at the
allocated memory. (Furthermore, if it contained the only copy of the pointer you
had, once it disappears, you'll have no way of freeing the memory, and no way
of using it, either. Using memory and freeing memory both require that you
have at least one pointer to the memory!)
12.2.4 Reallocating Memory Blocks
Sometimes you're not sure at first how much memory you'll need. For example,
if you need to store a series of items you read from the user, and if the only
way to know how many there are is to read them until the user types some
“end'' signal, you'll have no way of knowing, as you begin reading and storing
the first few, how many you'll have seen by the time you do see that “end''
marker. You might want to allocate room for, say, 100 items, and if the user
enters a 101st item before entering the “end'' marker, you might wish for a way
to say ”uh, malloc, remember those 100 items I asked for? Could I change my
mind and have 200 instead?''
In fact, you can do exactly this, with the realloc function. You hand realloc an
old pointer (such as you received from an initial call to malloc) and a new size,
and realloc does what it can to give you a chunk of memory big enough to hold
the new size. For example, if we wanted the ip variable from an earlier example
to point at 200 ints instead of 100, we could try calling
ip = realloc(ip, 200 * sizeof(int));
Since you always want each block of dynamically-allocated memory to be
contiguous (so that you can treat it as if it were an array), you and realloc have
to worry about the case where realloc can't make the old block of memory
bigger “in place,'' but rather has to relocate it elsewhere in order to find enough
contiguous space for the new requested size. realloc does this by returning a
new pointer. If realloc was able to make the old block of memory bigger, it
returns the same pointer. If realloc has to go elsewhere to get enough
contiguous memory, it returns a pointer to the new memory, after copying your
old data there. (In this case, after it makes the copy, it frees the old block.)
Finally, if realloc can't find enough memory to satisfy the new request at all, it
returns a null pointer. Therefore, you usually don't want to overwrite your old
pointer with realloc's return value until you've tested it to make sure it's not a
null pointer. You might use code like this:
int *newp;
newp = realloc(ip, 200 * sizeof(int));
if(newp != NULL)
ip = newp;
else {
printf("out of memory\n");
/* exit or return */
/* but ip still points at 100 ints */ }
If realloc returns something other than a null pointer, it succeeded, and we set
ip to what it returned. (We've either set ip to what it used to be or to a new
pointer, but in either case, it points to where our data is now.) If realloc returns
a null pointer, however, we hang on to our old pointer in ip which still points at
our original 100 values.
Putting this all together, here is a piece of code which reads lines of text from
the user, treats each line as an integer by calling atoi, and stores each integer
in a dynamically-allocated “array'':
#define MAXLINE 100
char line[MAXLINE];
int *ip;
int nalloc, nitems;
nalloc = 100;
ip = (int *)malloc(nalloc * sizeof(int)); /* initial allocation */
if(ip == NULL)
{
printf("out of memory\n");
exit(1);
}
nitems = 0;
ip[nitems++] = atoi(line);
}
We use two different variables to keep track of the “array'' pointed to by ip.
nalloc is how many elements we've allocated, and nitems is how many of them
are in use. Whenever we're about to store another item in the “array,'' if nitems
>= nalloc, the old “array'' is full, and it's time to call realloc to make it bigger.
Finally, we might ask what the return type of malloc and realloc is, if they are
able to return pointers to char or pointers to int or (though we haven't seen it
yet) pointers to any other type. The answer is that both of these functions are
declared (in <stdlib.h>) as returning a type we haven't seen, void * (that
15, pointer to void). We haven't really seen type void, either, but what's going
on here is that void * is specially defined as a “generic'' pointer type, which may
be used (strictly speaking, assigned to or from) any pointer type.
Therefore, a linked list is a list of elements in which the elements of the list can
be placed anywhere in memory, and these elements are linked with each other
using an explicit link field, that is, by storing the address of the next element in
the link field of the previous element.
Program 12.2: Here is a program for building and printing the elements
of the linked list
# include <stdio.h>
# include <stdlib.h> struct node
{
int data;
struct node *link;
};
struct node *insert(struct node *p, int n)
{
struct node *temp;
/* if the existing list is empty then insert a new node as the
starting node */
if(p==NULL)
{
p=(struct node *)malloc(sizeof(struct node)); /* creates new node data
value passes as parameter */
if(p==NULL)
{
printf("Error\n");
exit(0);
}
p-> data = n;
p-> link = p; /* makes the pointer pointing to itself because it
is a circular list*/
}
else
{
temp = p;
/* traverses the existing list to get the pointer to the last node of
it */
while (temp-> link != p)
temp = temp-> link;
void main()
{
int n;
int x;
struct node *start = NULL ;
printf("Enter the nodes to be created \n");
scanf("%d",&n);
while ( n -- > 0 )
Manipal University Jaipur Page No.: 205
Programming in C Unit 1
{
printf( "Enter the data values to be placed in a node\n");
scanf("%d",&x);
start = insert ( start, x );
}
printf("The created list is\n");
printlist ( start );
}
12.4.1 Inserting a node by using Recursive Programs
A linked list is a recursive data structure. A recursive data structure is a data
structure that has the same form regardless of the size of the data. You can
easily write recursive programs for such data structures.
Program 12.3
# include <stdio.h>
# include <stdlib.h>
struct node
{
int data;
struct node *link;
};
struct node *insert(struct node *p, int n)
{
struct node *temp;
if(p==NULL)
{
p=(struct node *)malloc(sizeof(struct node));
if(p==NULL)
{
printf("Error\n");
exit(0);
}
p-> data = n;
p-> link = NULL;
}
else
p->link = insert(p->link,n);/* the while loop replaced by recursive call */
return (p);
}
Manipal University Jaipur Page No.: 206
Programming in C Unit 1
{
Next = curr->link;
Curr -> link = prev;
Prev = curr;
Curr = next;
}
Program 12.4: Example in sorting lists.
# include <stdio.h>
# include <stdlib.h> struct node {
int data;
struct node *link;
};
struct node *insert(struct node *p, int n)
{
struct node *temp;
if(p==NULL)
{
p=(struct node *)malloc(sizeof(struct node));
if(p==NULL)
{
printf("Error\n");
exit(0);
}
p-> data = n;
p-> link = NULL;
}
else
{
temp = p;
while (temp-> link!= NULL)
temp = temp-> link;
temp-> link = (struct node *)malloc(sizeof(struct node));
if(temp -> link == NULL)
{
printf("Error\n");
exit(0);
}
temp = temp-> link;
Manipal University Jaipur Page No.: 208
Programming in C Unit 1
temp-> data = n;
temp-> link = null;
}
return(p);
}
void main()
{
int n;
int x;
struct node *start = NULL ;
Manipal University Jaipur Page No.: 210
Programming in C Unit 1
scanf("%d",&x);
start = insert ( start,x);
}
printf("The created list is\n");
printlist ( start );
start = sortlist(start);
printf("The sorted list is\n");
printlist ( start );
start = reverse(start);
printf("The reversed list is\n"); printlist ( start );
}
12.4.3 Deleting the specified node in a singly linked list
To delete a node, first we determine the node number to be deleted (this is
based on the assumption that the nodes of the list are numbered serially from
1 to n). The list is then traversed to get a pointer to the node whose number is
given, as well as a pointer to a node that appears before the node to be deleted.
Then the link field of the node that appears before the node to be deleted is
made to point to the node that appears after the node to be deleted, and the
node to be deleted is freed.
Program 12.5: Example of working with nodes.
# include <stdio.h>
# include <stdlib.h>
struct node *delet ( struct node *, int );
int length ( struct node * );
struct node
{
int data;
struct node *link;
};
struct node *insert(struct node *p, int n)
Manipal University Jaipur Page No.: 211
Programming in C Unit 1
{
struct node *temp;
if(p==NULL)
{
p=(struct node *)malloc(sizeof(struct node));
if(p==NULL)
{
printf("Error\n");
exit(0);
}
p-> data = n;
p-> link = NULL;
}
else
{
temp = p;
while (temp-> link != NULL)
temp = temp-> link;
temp-> link = (struct node *)malloc(sizeof(struct node));
if(temp -> link == NULL)
{
printf("Error\n");
exit(0);
}
temp = temp-> link;
temp-> data = n;
temp-> link = NULL;
}
return (p);
}
void printlist ( struct node *p )
{
printf("The data values in the list are\n");
while (p!= NULL)
{
printf("%d\t",p-> data);
p = p-> link;
}
Manipal University Jaipur Page No.: 212
Programming in C Unit 1
}
void main()
{
int n;
int x;
struct node *start = NULL;
printf("Enter the nodes to be created \n");
scanf("%d",&n);
while ( n-- > 0 )
{
printf( "Enter the data values to be placed in a node\n"); scanf("%d",&x);
start = insert ( start, x );
}
printf(" The list before deletion id\n");
printlist ( start );
printf("% \n Enter the node no \n");
scanf ( " %d",&n);
start = delet (start , n );
printf(" The list after deletion is\n");
printlist ( start );
}
/* a function to delete the specified node*/ struct node *delet ( struct node
*p, int node_no )
{
struct node *prev, *curr ;
int i;
if (p == NULL )
{
printf("There is no node to be deleted \n");
}
else
{
if ( node_no > length (p))
{
printf("Error\n");
}
Manipal University Jaipur Page No.: 213
Programming in C Unit 1
else
{
prev = NULL;
curr = p;
i=1;
while ( i < node_no )
{
prev = curr;
curr = curr-> link;
i = i+1;
}
if ( prev == NULL )
{
p = curr -> link; free ( curr );
} else {
prev -> link = curr -> link ; free ( curr );
}
}
} return(p);
}
/* a function to compute the length of a linked list */ int length ( struct node *p
)
{
int count = 0 ;
while ( p != NULL )
{
count++;
p = p->link;
}
return ( count ) ;
}
Self Assessment Questions
5. A linked list is a data structure that is used to model a dynamic list of data
items and is a collection of ______________ .
6. Linked list make use of ___________ memory allocation technique.
12.5 Summary
Manipal University Jaipur Page No.: 214
Programming in C Unit 1
3. ptr=ptr->link;
4. A linked list is a data structure that is used to model a dynamic list of data
items and is a collection of nodes. (Refer to section 12.4 for more details)
12.9 Exercises
1. Write a menu driven program to create a linked list of a class of students
and perform the following operations.
i. Write out the contents of a list
ii. Edit the details of a specified student
iii. Count the number of students above a specified age and weight
2. Write a program to insert a node after the specified node in alinkedlist.
3. Write a program to count the number of nodesin linked list.
4. Write a program to merge two sorted lists.
5. Explainbriefly how to represent polynomials using linked lists.Write a
program to add two polynomials.
Unit 13 File Management
Structure:
13.1 Introduction
Objectives
13.2 Defining and Openinga file
13.3 Closing Files
13.4 Input/Output Operationson Files
Predefined Streams
13.5 Error Handling during I/O Operations
13.6 Random Access to Files
13.7 Command Line Arguments
13.8 Summary
13.9 Terminal Questions
13.10 Answers to Self Assessment Questions
13.11 Answers to Terminal Questions
13.12 Exercises
13.1 Introduction
In nearly all the previous units, so far, we've been calling printf to print
formatted output to the “standard output'' (wherever that is). We've also been
Manipal University Jaipur Page No.: 216
Programming in C Unit 1
calling getchar to read single characters from the “standard input,'' and
putchar to write single characters to the standard output. “Standard input'' and
“standard output'' are two predefined I/O streams which are implicitly available
to us. In this unit, you will study how to take control of input and output by
opening our own streams, perhaps connected to data files, which we can read
from and write to.
Objectives:
After studying this unit, you should be able to:
• open your file and control input and output
• connect to file and able to read from the file and write to the file using file
pointers.
• checkg for error during I/O operations on files.
• implement the Random Access functions to allow control over the implied
read/write position in the file.
• supply a parameter to a program when the program is invoked using a
command line arguments.
13.2 Defining and Opening a File
How will we specify that we want to access a particular data file? It would
theoretically be possible to mention the name of a file each time it was desired
to read from or write to it. But such an approach would have a number of
drawbacks. Instead, the usual approach (and the one taken in C's stdio library)
is that you mention the name of the file once, at the time you open it.
Thereafter, you use some little token in this case, the file pointer which keeps
track (both for your sake and the library's) of which file you're talking about.
Whenever you want to read from or write to one of the files you're working with,
you identify that file by using its file pointer (that is, the file pointer you obtained
when you opened the file). As we'll see, you store file pointers in variables just
as you store any other data you manipulate, so it is possible to have several
files open, as long as you use distinct variables to store the file pointers.
You declare a variable to store a file pointer like this:
FILE *fp;
The type FILE is predefined for you by <stdio.h>. It is a data structure which
holds the information the standard I/O library needs to keep track of the file for
you. For historical reasons, you declare a variable which is a pointer to this
FILE type. The name of the variable can (as for any variable) be anything you
choose; it is traditional to use the letters fp in the variable name (since we're
talking about a file pointer). If you were reading from two files at once you'd
probably use two file pointers:
FILE *fp1, *fp2;
If you were reading from one file and writing to another you might declare an
input file pointer and an output file pointer:
FILE *ifp, *ofp;
Like any pointer variable, a file pointer isn't good until it's initialized to point to
something. (Actually, no variable of any type is much good until you've
initialized it.) To actually open a file, and receive the “token'' which you'll store
in your file pointer variable, you call fopen. fopen accepts a file name (as a
string) and a mode value indicating among other things whether you intend to
read or write this file. (The mode variable is also a string.) To open the file
input.dat for reading you might call
ifp = fopen("input.dat", "r");
The mode string "r" indicates reading. Mode "w" indicates writing, so we could
open output.dat for output like this:
ofp = fopen("output.dat", "w");
The other values for the mode string are less frequently used. The third major
mode is "a" for append. (If you use "w" to write to a file which already exists,
its old contents will be discarded.) You may also add “+’’ character to the mode
string to indicate that you want to both read and write, or a “b’’ character to
indicate that you want to do “binary'' (as opposed to text) I/O.
One thing to beware of when opening files is that it's an operation which may
fail. The requested file might not exist, or it might be protected against reading
or writing. (These possibilities ought to be obvious, but it's easy to forget them.)
fopen returns a null pointer if it can't open the requested file, and it's important
to check for this case before going off and using fopen's return value as a file
pointer. Every call to fopen will typically be followed with a test, like this:
ifp = fopen("input.dat", "r");
if(ifp == NULL)
{
printf("can't open file\n"); exit or return
Manipal University Jaipur Page No.: 218
Programming in C Unit 1
}
If fopen returns a null pointer, and you store it in your file pointer variable and
go off and try to do I/O with it, your program will typically crash.
It's common to collapse the call to fopen and the assignment in with the test:
if((ifp = fopen("input.dat", "r")) == NULL)
{
printf("can't open file\n");
exit or return
}
You don't have to write these “collapsed'' tests if you're not comfortable with
them, but you'll see them in other people's code, so you should be able to read
them.
Self Assessment Questions
1. The type FILE is predefined in the header file __________ .
2. We may add a “+’’ character to the mode string in the fopen function to
indicate that we want to both read and write. (True/False)
exits. (True/False)
}
if(c == EOF && nch == 0) return EOF;
line[nch] = '\0';
return nch;
}
Now we could read one line from ifp by calling char line[MAXLINE]; ...
fgetline(ifp, line, MAXLINE);
“standard error output''; error messages printed to it will not disappear into an
output file. For example, a more realistic way to print an error message when
a file can't be opened would be
if((ifp = fopen(filename, "r")) == NULL)
{
fprintf(stderr, "can't open file %s\n", filename); exit or return
}
where filename is a string variable indicating the file name to be opened. Not
only is the error message printed to stderr, but it is also more informative in
that it mentions the name of the file that couldn't be opened.
Program 13.2: To read a data file input.dat
Suppose you had a data file consisting of rows and columns of numbers:
1 2 34
5 6 78
9 10 112
Suppose you wanted to read these numbers into an array. (Actually, the array
will be an array of arrays, or a “multidimensional'' array; ) We can write code to
do this by putting together several pieces: the fgetline function we just showed,
and the getwords function from which each line broken into words. Assuming
that the data file is named input.dat, the code would look like this:
#define MAXLINE 100
#define MAXROWS 10
#define MAXCOLS 10
int array[MAXROWS][MAXCOLS];
char *filename = "input.dat";
FILE *ifp;
char line[MAXLINE];
char *words[MAXCOLS];
int nrows = 0;
int n;
int i;
{
fprintf(stderr, "can't open %s\n", filename);
exit(EXIT_FAILURE);
} while(fgetline(ifp, line, MAXLINE) != EOF) { if(nrows >=
MAXROWS) { fprintf(stderr, "too many rows\n"); exit(EXIT_FAILURE);
}
fclose(stdout);
if(fgetc(stdout) >= 0){
fprintf(stderr, "What - no error!\n"); exit(EXIT_FAILURE);
}
perror("fgetc");
exit(EXIT_SUCCESS);
}
/* Result */
fgetc: Bad file number
immediately following the end of the data just written. (Remember that stdio
insists on the user inserting a buffer-flushing operation between each element
of a read-write-read cycle.) To control this, the Random Access functions allow
control over the implied read/write position in the file. The file position indicator
is moved without the need for a read or a write, and indicates the byte to be
the subject of the next operation on the file.
Three types of function exist which allow the file position indicator to be
examined or changed. Their declarations and descriptions follow.
#include <stdio.h> /* return file position indicator */ long ftell(FILE *stream);
int fgetpos(FILE *stream, fpos_t *pos);
/* set file position indicator */ int fseek(FILE *stream, long offset, int ptrname);
int fsetpos(FILE *stream, const fpos_t *pos);
ftell returns the current value (measured in characters) of the file position
indicator if stream refers to a binary file. For a text file, a ‘magic’ number is
returned, which may only be used on a subsequent call to fseek to reposition
to the current file position indicator. On failure, -1L is returned and errno is set.
rewind sets the current file position indicator to the start of the file indicated by
stream. The file's error indicator is reset by a call of rewind. No value is
returned.
fseek allows the file position indicator for stream to be set to an arbitrary value
(for binary files), or for text files, only to a position obtained from ftell, as follows:
• In the general case, the file position indicator is set to offset bytes
(characters) from a point in the file determined by the value of ptrname.
Offset may be negative. The values of ptrname may be SEEK_SET, which
sets the file position indicator relative to the beginning of the file,
SEEK_CUR, which sets the file position indicator relative to its current
value, and SEEK_END, which sets the file position indicator relative to the
end of the file. The latter is not necessarily guaranteed to work properly on
binary streams.
• For text files, offset must either be zero or a value returned from a previous
call to ftell for the same stream, and the value of ptrname must be
SEEK_SET.
• fseek clears the end of file indicator for the given stream and erases the
memory of any ungetc. It works for both input and output.
• Zero is returned for success, non-zero for a forbidden request.
Note that for ftell and fseek it must be possible to encode the value of the file
position indicator into a long. This may not work for very long files, so the
Standard introduces fgetpos and fsetpos which have been specified in a way
that removes the problem.
fgetpos stores the current file position indicator for stream in the object pointed
to by pos. The value stored is ‘magic’ and only used to return to the specified
position for the same stream using fsetpos.
fsetpos works as described above, also clearing the stream's end-of-file
indicator and forgetting the effects of any ungetc operations.
For both functions, on success, zero is returned; on failure, non-zero is
returned and errno is set.
Self Assessment Questions
10. function returns the current value (measured in
characters) of the file position indicator if stream refers to a binary file.
11. fseek allows the file position indicator for stream to be set to an arbitrary
value. (True/False)
12. stores the current file position indicator for stream in the
object pointed to by pos.
command line includes the command you typed to invoke your program (that
is, the name of your program). In other words, argv[0] typically points to the
name of your program, and argv[1] is the first argument.
There are no hard-and-fast rules for how a program should interpret its
command line. There is one set of conventions for Unix, another for MS- DOS,
another for VMS. Typically you'll loop over the arguments, perhaps treating
some as option flags and others as actual arguments (input files, etc.),
interpreting or acting on each one. Since each argument is a string, you'll have
to use strcmp or the like to match arguments against any patterns you might
be looking for. Remember that argc contains the number of words on the
command line, and that argv[0] is the command name, so if argc is 1, there are
no arguments to inspect. (You'll never want to look at argv[i], for i >= argc,
because it will be a null or invalid pointer.)
A further example of the use of argc and argv now follows:
fclose(fp); }
return 0;
}
As a historical note, the Unix cat program is so named because it can be used
to concatenate two files together, like this:
cat a b > c
This illustrates why it's a good idea to print error messages to stderr, so that
they don't get redirected. The “can't open file'' message in this example also
includes the name of the program as well as the name of the file.
Yet another piece of information which it's usually appropriate to include in
error messages is the reason why the operation failed, if known. For operating
system problems, such as inability to open a file, a code indicating the error is
often stored in the global variable errno. The standard library function strerror
will convert an errno value to a human-readable error message string.
} fclose(fp);
}
Self Assessment Questions
13. Command line argument is a parameter supplied to a program when the
program is invoked. (True/False)
14. In the command line arguments of main(), argv is an array-of-pointers- to-
___________ .
15. The ____________ is a parameter supplied to a program when the
program is invoked.
13.8 Summary
“Standard input'' and “standard output'' are two predefined I/O streams which
are implicitly available to us. To actually open a file, and receive the “token''
which you'll store in your file pointer variable, you call fopen. fopen accepts a
file name (as a string) and a mode value indicating among other things whether
you intend to read or write this file. The file pointer which keeps track (both for
your sake and the library's) of which file you're talking about.
For each of the I/O library functions we've there's a companion function which
accepts an additional file pointer argument telling it where to read from or write
to. The standard I/O functions maintain two indicators with each open stream
to show the end-of-file and error status of the stream. The Random Access
functions allow control over the implied read/write position in the file. The
command line argument is a parameter supplied to a program when the
program is invoked.
fclose(pt1);
fclose(pt2);
}
a) Read the string represented by name from the data file sample.old.
b) Display it on the screen
c) Enter the updated string.
d) Write the new string to the data file sample.new
2. What is a command line argument and what is its use? Explain
13.12 Exercises