Special Functions for Working with Files in C Programming Language

It is very likely that many of the programs you will develop will be able to perform all their I/O  operations using just the getchar, putchar, scanf, and printf functions and the notion of I/O  redirection. However, situations do arise when you need more flexi- bility to work with files. For example, you might need to read data from two or more different files or to write output results into several different files. To handle these situa- tions, special functions have been designed expressly for working with files. Several of these functions are described in the following sections.

1. The fopen Function

Before you can begin to do any I/O  operations on a file, the file must first be opened. To open a file, you must specify the name of the file. The system then checks to make cer- tain that this file actually exists and, in certain instances, creates the file for you if it does not. When a file is opened, you must also specify to the system the type of I/O  opera- tions that you intend to perform with the file. If the file is to be used to read in data,

you normally open the file in read mode. If you want to write data into the file, you open the file in write mode. Finally, if you want to append information to the end of a file that already contains some data, you open the file in append mode. In the latter two cases, write and append mode, if the specified file does not exist on the system, the system cre- ates the file for you. In the case of read mode, if the file does not exist, an error occurs.

Because a program can have many different files open at the same time, you need a way to identify a particular file in your program when you want to perform some I/O operation on the file. This is done by means of a file pointer.

The function called fopen in the standard library serves the function of opening a file on the system and of returning a unique file pointer with which to subsequently identify the file. The function takes two arguments: The first is a character string specifying the name of the file to be opened; the second is also a character string that indicates the mode in which the file is to be opened. The function returns a file pointer that is used by other library functions to identify the particular file.

If the file cannot be opened for some reason, the function returns the value NULL, which is defined inside the header file <stdio.h>. Also defined in this file is the defini- tion of a type called FILE. To store the result returned by the fopen function in your program, you must define a variable of type “pointer to FILE.”

If you take the preceding comments into account, the statements

#include <stdio.h>

FILE *inputFile;

inputFile = fopen (“data”, “r”);

have the effect of opening a file called data in read mode. (Write mode is specified by the string “w”, and append mode is specified by the string “a”.) The fopen call returns an identifier for the opened file that is assigned to the FILE pointer variable inputFile. Subsequent testing of this variable against the defined value NULL, as in the following:

if ( inputFile == NULL )

printf (“*** data could not be opened.\n”);

else

// read the data from the file

tells you whether the open was successful.

You should always check the result of an fopen call to make certain it succeeds. Using a NULL pointer can produce unpredictable results.

Frequently, in the fopen call, the assignment of the returned FILE pointer variable and the test against the NULL pointer are combined into a single statement,  as follows:

if ( (inputFile = fopen (“data”, “r”)) == NULL )

printf (“*** data could not be opened.\n”);

The fopen function also supports three other types of modes, called update modes (“r+”, “w+”, and “a+”). All three update modes permit both reading and writing operations to be performed on a file. Read update (“r+”) opens an existing file for both reading and writing. Write update (“w+”) is like write mode (if the file already exists, the contents are destroyed; if one doesn’t exist, it’s created), but once again both reading and writing are permitted. Append update (“a+”) opens an existing file or creates a new one if one doesn’t exist. Read operations can occur anywhere in the file, but write operations can only add data to the end.

Under operating systems such as Windows, which distinguish text files from binary files, a b must be added to the end of the mode string to read or write a binary file. If you forget to do this, you will get strange results, even though your program will still run. This is because on these systems, carriage return/line feed character pairs are con- verted to return characters when they are read from or written to text files. Furthermore, on input, a file that contains a Ctrl+Z character causes an end-of-file con- dition if the file was not opened as a binary file. So,

inputFile = fopen (“data”, “rb”);

opens the binary file data for reading.

2. The getc and putc Functions

The function getc enables you to read in a single character from a file. This function behaves identically to the getchar function described previously. The only difference is that getc takes an argument: a FILE pointer that identifies the file from which the char- acter is to be read. So, if fopen is called as shown previously, then subsequent execution of the statement

c = getc (inputFile);

has the effect of reading a single character from the file data. Subsequent characters can be read from the file simply by making additional calls to the getc function.

The getc function returns the value EOF when the end of file is reached, and as with the getchar function, the value returned by getc should be stored in a variable of type int.

As you might have guessed, the putc function is equivalent to the putchar function, only it takes two arguments instead of one. The first argument to putc is the character that is to be written into the file. The second argument is the FILE pointer. So the call

putc (‘\n’, outputFile);

writes a newline character into the file identified by the FILE pointer outputFile. Of course, the identified file must have been previously opened in either write or append mode (or in any of the update modes) for this call to succeed.

3. The fclose Function

One operation that you can perform on a file, which must be mentioned, is that of clos- ing the file. The fclose function, in a sense, does the opposite of what the fopen does:

It tells the system that you no longer need to access the file. When a file is closed, the system performs some necessary housekeeping chores (such as writing all the data that it might be keeping in a buffer in memory to the file) and then dissociates the particular file identifier from the file. After a file has been closed, it can no longer be read from or written to unless it is reopened.

When you have completed your operations on a file, it is a good habit to close the file. When a program terminates normally, the system automatically  closes any open files for you. It is generally better programming practice to close a file as soon as you are done with it. This can be beneficial if your program has to deal with a large number of files, as there are practical limits on the number of files that can be kept simultaneously open by a program.Your system might have various limits on the number of files that you can have open simultaneously. This might only be an issue if you are working with multiple files in your program.

By the way, the argument to the fclose function is the FILE pointer of the file to be closed. So, the call

fclose (inputFile);

closes the file associated with the FILE pointer inputFile.

With the functions fopen, putc, getc, and fclose, you can now proceed to write a program that will copy one file to another. Program 16.3 prompts the user for the name of the file to be copied and the name of the resultant copied file. This program is based upon Program 16.2.You might want to refer to that program for comparison purposes.

Assume that the following three lines of text have been previously typed into the file copyme:

This is a test of the file copy program

that we have just developed using the

fopen, fclose, getc, and putc functions.

Program 16.3     Copying  Files

// Program to copy one file to another

#include <stdio.h>

int main (void)

{

char inName[64], outName[64];

FILE *in, *out;

int  c;

 

// get file names from user

printf (“Enter name of file to be copied: “);

scanf (“%63s”, inName);

printf (“Enter name of output file: “);

scanf (“%63s”, outName);

 

// open input and output files

if ( (in = fopen (inName, “r”)) == NULL ) {

printf (“Can’t open %s for reading.\n”, inName);

return 1;

}

if ( (out = fopen (outName, “w”)) == NULL ) {

printf (“Can’t open %s for writing.\n”, outName);

return 2;

}

// copy in to out

while ( (c = getc (in)) != EOF )

putc (c, out);

// Close open files

fclose (in);

fclose (out);

printf (“File has been copied.\n”);

return 0;

}

Program 16.3   Output

Enter name of file to be copied: copyme

Enter name of output file: here

File has been copied.

Now examine the contents of the file here. The file should contain the same three lines of text as contained in the copyme file.

The scanf function call in the beginning of the program is given a field-width count of 63 just to ensure that you don’t overflow your inName or outName character arrays. The program then opens the specified input file for reading and the specified output file for writing. If the output file already exists and is opened in write mode, its previous contents are overwritten on most systems.

If either of the two fopen calls is unsuccessful, the program displays an appropriate message at the terminal and proceeds no further, returning a nonzero exit status to indi- cate the failure. Otherwise, if both opens succeed, the file is copied one character at a time by means of successive getc and putc calls until the end of the file is encountered. The program then closes the two files and returns a zero exit status to indicate success.

4. The feof Function

To test for an end-of-file condition on a file, the function feof is provided. The single argument to the function is a FILE pointer. The function returns an integer value that is nonzero if an attempt has been made to read past the end of a file, and is zero otherwise. So, the statements

if ( feof (inFile) ) {

printf (“Ran out of data.\n”);

return 1;

}

have the effect of displaying the message “Ran out of data” at the terminal if an end-of- file condition exists on the file identified by inFile.

Remember, feof tells you that an attempt has been made to read past the end of the file, which is not the same as telling you that you just read the last data item from a file. You have to read one past the last data item for feof to return nonzero.

5. The fprintf and fscanf Functions

The functions fprintf and fscanf are provided to perform the analogous operations of the printf and scanf functions on a file. These functions take an additional argument, which is the FILE pointer that identifies the file to which the data is to be written or from which the data is to be read. So, to write the character string “Programming in C is fun.\n” into the file identified by outFile, you can write the following statement:

fprintf (outFile, “Programming in C is fun.\n”);

Similarly, to read in the next floating-point value from the file identified by inFile into the variable fv, the statement

fscanf (inFile, “%f”, &fv);

can be used. As with scanf, fscanf returns the number of arguments that are successful- ly read and assigned or the value EOF, if the end of the file is reached before any of the conversion specifications have been processed.

6. The fgets and fputs Functions

For reading and writing entire lines of data from and to a file, the fputs and fgets functions can be used. The fgets function is called as follows:

fgets (buffer, n, filePtr);

buffer is a pointer to a character array where the line that is read in will be stored; n is an integer value that represents the maximum number of characters to be stored into buffer; and filePtr identifies the file from which the line is to be read.

The fgets function reads characters from the specified file until a newline character has been read (which will get stored in the buffer) or until n-1 characters have been read, whichever occurs first. The function automatically places a null character after the last character in buffer. It returns the value of buffer (the first argument) if the read is suc- cessful, and the value NULL if an error occurs on the read or if an attempt is made to read past the end of the file.

fgets can be combined with sscanf (see Appendix B) to perform line-oriented reading in a more orderly and controlled fashion than by using scanf alone.

The fputs function writes a line of characters to a specified file. The function is called as follows:

fputs (buffer, filePtr);

Characters stored in the array pointed to by buffer are written to the file identified by filePtr until the null character is reached. The terminating null character is not written to the file.

There are also analogous functions called gets and puts that can be used to read a line from the terminal and write a line to the terminal, respectively. These functions are described in Appendix B.

7. stdin, stdout, and stderr

When a C program is executed, three files are automatically opened by the system for use by the program. These files are identified by the constant FILE pointers stdin, stdout, and stderr, which are defined in <stdio.h>. The FILE pointer stdin identifies the standard input of the program and is normally associated with your terminal win- dow. All standard I/O  functions that perform input and do not take a FILE pointer as an argument get their input from stdin. For example, the scanf function reads its input from stdin, and a call to this function is equivalent to a call to the fscanf function with stdin as the first argument. So, the call

fscanf (stdin, “%i”, &i);

reads in the next integer value from the standard input, which is normally your terminal window. If the input to your program has been redirected to a file, this call reads the next integer value from the file to which the standard input has been redirected.

As you might have guessed, stdout refers to the standard output, which is normally also associated with your terminal window. So, a call such as

printf (“hello there.\n”);

can be replaced by an equivalent call to the fprintf function with stdout as the first argument:

fprintf (stdout, “hello there.\n”);

The FILE pointer stderr identifies the standard error file. This is where most of the error messages produced by the system are written and is also normally associated with your terminal window. The reason stderr exists is so that error messages can be logged to a device or file other than where the normal output is written. This is particularly desirable when the program’s output is redirected to a file. In such a case, the normal output is written into the file, but any system error messages still appear in your window. You might want to write your own error messages to stderr for this same reason. As an example, the fprintf call in the following statement:

if ( (inFile = fopen (“data”, “r”)) == NULL )

{

fprintf (stderr, “Can’t open data for reading.\n”);

}

writes the indicated error message to stderr if the file data cannot be opened for read- ing. In addition, if the standard output has been redirected to a file, this message still appears in your window.

8. The exit Function

At times, you might want to force the termination of a program, such as when an error condition is detected by a program.You know that program execution is automatically terminated whenever the last statement in main is executed or when executing a return from main. To explicitly terminate a program, no matter from what point you are exe- cuting, the exit function can be called. The function call

exit (n);

has the effect of terminating (exiting from) the current program. Any open files are auto- matically closed by the system. The integer value n is called the exit status, and has the same meaning as the value returned from main.

The standard header file <stdlib.h> defines EXIT_FAILURE as an integer value that you can use to indicate the program has failed and EXIT_SUCCESS to be one that you can use to indicate it has succeeded.

When a program terminates simply by executing the last statement in main, its exit status is undefined. If another program needs to use this exit status, you mustn’t let this happen. In such a case, make certain that you exit or return from main with a defined exit status.

As an example of the use of the exit function, the following function causes the pro- gram to terminate with an exit status of EXIT_FAILURE if the file specified  as its argu- ment cannot be opened for reading. Naturally, you might want to return the fact that the open failed instead of taking such a drastic action by terminating the program.

#include <stdlib.h>

#include <stdio.h>

FILE *openFile (const char *file)

{

FILE *inFile;

if ( (inFile = fopen (file, “r”)) == NULL ) {

fprintf (stderr, “Can’t open %s for reading.\n”, file);

exit (EXIT_FAILURE);

}

return inFile;

}

Remember that there’s no real difference between exiting or returning from main. They both terminate the program, sending back an exit status. The main difference between exit and return is when they’re executed from inside a function other than main. The exit call terminates the program immediately whereas return simply transfers control back to the calling routine.

9. Renaming  and Removing Files

The rename function from the library can be used to change the name of a file. It takes two arguments: the old filename and the new filename. If for some reason the renaming operation fails (for example, if the first file doesn’t exist, or the system doesn’t allow you to rename the particular file), rename returns a nonzero value. The code

if ( rename (“tempfile”, “database”) ) {

fprintf (stderr, “Can’t rename tempfile\n”);

exit (EXIT_FAILURE);

}

renames the file called tempfile to database and checks the result of the operation to ensure it succeeded.

The remove function deletes the file specified by its argument. It returns a nonzero value if the file removal fails. The code

if ( remove (“tempfile”) )

{

fprintf (stderr, “Can’t remove tempfile\n”);

exit (EXIT_FAILURE);

}

attempts to remove the file tempfile and writes an error message to standard error and exit if the removal fails.

Incidentally, you might be interested in using the perror function to report errors from standard library routines. For more details, consult Appendix B.

This concludes our discussion of I/O  operations under C. As mentioned, not all of the library functions are covered here due to lack of space. The standard C library con- tains a wide selection of functions for performing operations with character strings, for random I/O, mathematical calculations, and dynamic memory management. Appendix B lists many of the functions inside this library.

Source: Kochan Stephen G. (2004), Programming in C: A Complete Introduction to the C Programming Language, Sams; Subsequent edition.

Leave a Reply

Your email address will not be published. Required fields are marked *