Other Standard IO Functions

I l @ ve RuBoard

Other Standard I/O Functions

The ANSI standard library contains over three dozen functions in the standard I/O family. Although we don't cover them all here, we will briefly describe a few more to give you a better idea of what is available. We'll list each function by its ANSI C prototype to indicate its arguments and return values. Of those functions we discuss here, all but setvbuf () are also available in pre-ANSI implementations . Appendix F, "The Standard ANSI C Library," lists the full ANSI C standard I/O package.

The int ungetc (int c, FILE *fp) Function

The int ungetc(int c, FILE *fp) function pushes the character specified by c back onto the input stream. If you push a character onto the input stream, the next call to a standard input function reads that character (see Figure 12.2). Suppose, for example, you want a function to read characters up to, but not including, the next colon . You can use getchar () or getc() to read characters until a colon is read and then use ungetc() to place the colon back in the input stream. The ANSI C standard guarantees only one pushback at a time. If an implementation permits you to push back several characters in a row, the input functions read them in the reversed order of pushing.

Figure 12.2. The ungetc() function.
graphics/12fig02.jpg

The int fflush (FILE *fp) Function

Calling the int fflush(FILE *fp) function causes any unwritten data in the buffer to be sent to the output file identified by fp . This process is called flushing a buffer . If fp is the null pointer, all output buffers are flushed.

The int setvbuf(FILE *fp, char *buf, int mode, size_t size ) Function

The int setvbuf(FILE *fp, char *buf, int mode, size_t size) function sets up an alternative buffer to be used by the standard I/O functions. It is called after the file has been opened and before any other operations have been performed on the stream. The pointer fp identifies the stream, and buf points to the storage to be used. If the value of buf is not NULL , you must create the buffer. For instance, you could declare an array of 1,024 chars and pass the address of that array. However, if you use NULL for the value of buf , the function allocates a buffer itself. The size variable tells setvbuf() how big the array is. (The size_t type is a derived integer type; see Chapters 11 and 16, "The C Preprocessor and the C Library." ) The mode is selected from the following choices: _IOFBF means fully buffered, _IOLBF means line-buffered, and _IONBF means nonbuffered. The function returns zero if successful, nonzero otherwise .

Suppose you had a program that worked with stored data objects having, say, a size of 3,000 bytes each. You could use setvbuf() to create a buffer whose size matched that of the data object. Many pre-ANSI implementations use setbuf () instead. It takes two arguments. The first is a pointer-to- FILE to identify the file to be buffered. The second is the desired buffer size in bytes. The setbuf() function does not select among different buffering modes, nor does it have a return value.

Binary I/O: fread() and fwrite()

The fread() and fwrite() functions are next on the list, but first some background. The standard I/O functions you've used to this point are text oriented, dealing with characters and strings. What if you want to save numeric data in a file? True, you can use fprintf() and the %f format to save a floating-point value, but then you are saving it as a string. For instance, the sequence

 double num = 1./3.; fprintf(fp,"%f", num); 

saves num as a string of eight characters: 0.333333 . Using a %.2f specifier saves it as four characters: 0.33 . Using a %.12f specifier saves it as 14 characters: 0.333333333333 . Changing the specifier alters the amount of space needed to store the value; it can also result in different values being stored. After the value of num is stored as 0.33 , there is no way to get back the full precision when reading the file. In general, fprintf() converts numeric values to strings, possibly altering the value.

The most accurate and consistent way to store a number is to use the same pattern of bits that the program does. Therefore, a double value should be stored in a size double container. When data is stored in a file using the same representation that the program uses, we say that the data is stored in binary form . There is no conversion from numeric forms to strings. For standard I/O, the fread() and fwrite() functions provide this binary service (see Figure 12.3).

Figure 12.3. Binary and text output.
graphics/12fig03.jpg

Actually, all data is stored in binary form. Even characters are stored using the binary representation of the character code. However, if all data in the file is interpreted as character codes, we say that the file contains text data. If some or all of the data is interpreted as numeric data in binary form, we say that the file contains binary data. (Also, files in which the data represents machine language instructions are binary files.)

The uses of the terms binary and text can get confusing. ANSI C recognizes two modes for opening files: binary and text. Many operating systems recognize two file formats: binary and text. Information can be stored or read as binary data or as text data. These are all related , but not identical. You can open a text format file in the binary mode. You can store text in a binary format file. You can use getc() to copy files containing binary data. In general, however, you use the binary mode to store binary data in a binary format file. Similarly, you most often use text data in text files opened in the text format.

The size_t fwrite(void *ptr, size_t size, size_t nmemb, FILE *fp) Function

The size_t fwrite(void *ptr, size_t size, size_t nmemb, FILE *fp) function writes binary data to a file. The size_t type is defined in terms of the standard C types. It is the type returned by the sizeof operator. Typically it is unsigned int , but an implementation can choose another type. The pointer ptr is the address of the chunk of data to be written. The size represents the size, in bytes, of the chunks to be written, and the nmemb represents the number of chunks to be written. As usual, fp identifies the file to be written to. For instance, to save a data object (such as an array) that is 256 bytes in size, you can do this:

 char buffer[256]; fwrite(buffer, 256, 1, fp); 

This call writes one chunk of 256 bytes from buffer to the file, or to save an array of 10 double values, you can do this:

 double earnings[10]; fwrite(earnings, sizeof (double), 10, fp); 

This call writes data from the earnings array to the file in 10 chunks, each of size double .

You probably noticed the odd declaration of void *ptr in the fwrite() prototype. One problem with fwrite() is that its first argument is not a fixed type. For instance, the first example used buffer , which is type pointer-to- char , and the second example used earnings , which is type pointer-to- double . Under ANSI C function prototyping, these actual arguments are converted to the pointer-to- void type, which acts as a sort of catchall type for pointers. (Pre-ANSI C uses type char * for this argument, requiring you to typecast actual arguments to that type.)

The fwrite() function returns the number of items successfully written. Normally, this equals nmemb , but it can be less if there is a write error.

The size_t fread(void *ptr, size_t size, size_t nmemb, FILE *fp) Function

The size_t fread(void *ptr, size_t size, size_t nmemb, FILE *fp) function takes the same set of arguments that fwrite() does. This time ptr is the address of the memory storage into which file data is read, and fp identifies the file to be read. Use this function to read data that was written to a file using fwrite() . For example, to recover the array of 10 double s saved in the previous example, use this call:

 double earnings[10]; fread(earnings, sizeof (double), 10, fp); 

This call copies 10 size double values into the earnings array.

The fread() function returns the number of items successfully read. Normally, this equals nmemb , but it can be less if there is a read error or if the end of file is reached.

The int feof(FILE *fp) and int ferror (FILE *fp) Functions

When the standard input functions return EOF , it usually means they have reached the end of a file. However, it can also indicate that a read error has occurred. The feof() and ferror() functions enable you to distinguish between the two possibilities. The feof() function returns a non-zero value if the last input call detected the end of file and returns zero otherwise. The ferror() function returns a non-zero value if a read or write error has occurred and zero otherwise.

An Example

Let's use some of these functions in a program that appends the contents from a list of files to the end of another file. One problem is passing the file information to the program. This can be done interactively or by using command-line arguments. We'll take the first approach, which suggests a plan along the following lines:

  • Request a name for the destination file and open it.

  • Use a loop to request source files.

  • Open each source file in turn in the read mode and add it to the append file.

To illustrate setvbuf() , we'll use it to specify a different buffer size. The next stage of refinement examines opening the append file. We will use the following steps:

  • Open the final command-line file in the append mode.

  • If this cannot be done, quit.

  • Establish a 1,024-byte buffer for this file.

  • If this cannot be done, quit.

Similarly, we can refine the copying portion by doing the following for each file:

  • If it is the same as the append file, skip to the next file.

  • If it cannot be opened in the read mode, skip to the next file.

  • Add the contents of the file to the append file.

For practice, we'll use fread() and fwrite() for the copying. Listing 12.6 shows the result.

Listing 12.6 The append.c program.
 /* append.c -- appends files to a file */ #include <stdio.h> #include <stdlib.h> #include <string.h> #define BUFSIZE 1024 #define SLEN 50 char temp[BUFSIZE]; void append(FILE *source, FILE *dest); int main(void) {     FILE *fa, *fs;     int files = 0;     char file_app[SLEN];     char file_src[SLEN];     puts("Enter name of destination file:");     gets(file_app);     if ((fa = fopen(file_app, "a")) == NULL)     {         fprintf(stderr, "Can't open %s\n", file_app);         exit(2);     }     if (setvbuf(fa, NULL, _IOFBF, BUFSIZE) != 0)     {         fputs("Can't create output buffer\n", stderr);         exit(3);     }     puts("Enter name of first source file (empty line to quit):");     while (gets(file_src) && file_src[0] != ' 
 /* append.c -- appends files to a file */ #include <stdio.h> #include <stdlib.h> #include <string.h> #define BUFSIZE 1024 #define SLEN 50 char temp[BUFSIZE]; void append(FILE *source, FILE *dest); int main(void) { FILE *fa, *fs; int files = 0; char file_app[SLEN]; char file_src[SLEN]; puts("Enter name of destination file:"); gets(file_app); if ((fa = fopen(file_app, "a")) == NULL) { fprintf(stderr, "Can't open %s\n", file_app); exit(2); } if (setvbuf(fa, NULL, _IOFBF, BUFSIZE) != 0) { fputs("Can't create output buffer\n", stderr); exit(3); } puts("Enter name of first source file (empty line to quit):"); while (gets(file_src) && file_src[0] != '\0') { if (strcmp(file_src, file_app) == 0) fputs("Can't append file to itself\n",stderr); else if ((fs = fopen(file_src, "r")) == NULL) fprintf(stderr, "Can't open %s\n", file_src); else { if (setvbuf(fs, NULL, _IOFBF, BUFSIZE) != 0) { fputs("Can't create output buffer\n",stderr); continue; } append(fs, fa); if (ferror(fs) != 0) fprintf(stderr,"Error in reading file %s.\n", file_src); if (ferror(fa) != 0) fprintf(stderr,"Error in writing file %s.\n", file_app); fclose(fs); files++; printf("File %s appended.\n", file_src); puts("Next file (empty line to quit):"); } } printf("Done. %d files appended.\n", files); fclose(fa); return 0; } void append(FILE *source, FILE *dest) { size_t bytes; extern char temp[]; /* use the external temp array */ while ((bytes = fread(temp,sizeof(char),BUFSIZE,source)) > 0) fwrite(temp, sizeof (char), bytes, dest); } 
') { if (strcmp(file_src, file_app) == 0) fputs("Can't append file to itself\n",stderr); else if ((fs = fopen(file_src, "r")) == NULL) fprintf(stderr, "Can't open %s\n", file_src); else { if (setvbuf(fs, NULL, _IOFBF, BUFSIZE) != 0) { fputs("Can't create output buffer\n",stderr); continue; } append(fs, fa); if (ferror(fs) != 0) fprintf(stderr,"Error in reading file %s.\n", file_src); if (ferror(fa) != 0) fprintf(stderr,"Error in writing file %s.\n", file_app); fclose(fs); files++; printf("File %s appended.\n", file_src); puts("Next file (empty line to quit):"); } } printf("Done. %d files appended.\n", files); fclose(fa); return 0; } void append(FILE *source, FILE *dest) { size_t bytes; extern char temp[]; /* use the external temp array */ while ((bytes = fread(temp,sizeof(char),BUFSIZE,source)) > 0) fwrite(temp, sizeof (char), bytes, dest); }

The following code creates a buffer 1024 bytes in size to be used with the append file:

 if (setvbuf(fa, NULL, _IOFBF, BUFSIZE) != 0) {     fputs("Can't create output buffer\n", stderr);     exit(3);    } 

If setvbuf() is unable to create the buffer, it returns a non-zero value, and the code then terminates the program. Similar coding establishes a 1,024-byte buffer for the file currently being copied . By using NULL as the second argument to setvbuf() , we let that function allocate storage for the buffer. This code prevents the program from trying to append a file to itself:

 if (strcmp(file_src, file_app) == 0)     fputs("Can't append file to itself\n",stderr); 

The argument file_app represents the name of the destination file, and file_src represents the name of the file currently being processed .

The append() function does the copying. Instead of copying a byte at a time, it uses fread() and fwrite() to copy 1024 bytes at a time:

 void append(source, dest) FILE *source, *dest; {     size_t bytes;     extern char temp[];     while ((bytes = fread(temp,sizeof(char),BUFSIZE,source)) > 0)         fwrite(temp, sizeof (char), bytes, dest);    } 

Because the file specified by dest is opened in the append mode, each source file is added to the end of the destination file, one after the other.

The example uses text-mode files; by using the "ab" and "rb" modes, it could handle binary files.

Random Access with Binary I/O

Random access is most often used with binary files written using binary I/O, so let's look at a short example. The program in Listing 12.7 creates a file of double numbers , and then lets you access the contents.

Listing 12.7 The randbin.c program.
 /* randbin.c -- random access, binary i/o */ #include <stdio.h> #include <stdlib.h> #define ARSIZE 1000 int main() {     double numbers[ARSIZE];     double value;     const char * file = "numbers.dat";     int i;     long pos;     FILE *iofile;     /* create a set of double values */     for(i = 0; i < ARSIZE; i++)         numbers[i] = 100.0 * i + 1.0 / (i + 1);     if ((iofile = fopen(file, "wb")) == NULL)     {         fprintf(stderr, "Could not open %s for output.\n", file);         exit(1);     }     /* write array in binary format to file */     fwrite(numbers, sizeof (double), ARSIZE, iofile);     fclose(iofile);     if ((iofile = fopen(file, "rb")) == NULL)     {         fprintf(stderr,             "Could not open %s for random access.\n", file);         exit(1);     }     /* read selected items from file */     printf("Enter an index in the range 0-%d.\n", ARSIZE - 1);     scanf("%d", &i);     while (i >= 0 && i < ARSIZE)     {         pos = (long) i * sizeof(double); /* find offset */         fseek(iofile, pos, SEEK_SET);    /* go there    */         fread(&value, sizeof (double), 1, iofile);         printf("The value there is %f.\n", value);         printf("Next index (out of range to quit):\n");         scanf("%d", &i);     }     fclose(iofile);     puts("Bye!");     return 0;    } 

First, the program creates an array and places some values into it. Then it creates a file called numbers.dat in binary mode and uses fwrite() to copy the array contents to the file. The 64-bit pattern for each double value is copied from memory to the file. You can't read the resulting binary file with a text editor because the values are not translated to strings. However, each value is stored in the file precisely as it was stored in memory, so there is no loss of precision. Furthermore, each value occupies exactly 64 bits of storage in the file, so it is a simple matter to calculate the location of each value. The second part of the program opens the file for reading and asks the user to enter the index for a value. Multiplying the index by the number of bytes per double yields the location in the file. The program then uses fseek() to go to that location and fread() to read the value there. Note that there are no format specifiers. Instead, fread() copies the 8 bytes starting at that location into the memory location indicated by &value . Then it can use printf() to display value . Here is a sample run:

 Enter an index in the range 0-999.  500  The value there is 50000.001996. Next index (out of range to quit):  900  The value there is 90000.001110. Next index (out of range to quit):   The value there is 1.000000. Next index (out of range to quit): -1 Bye! 
I l @ ve RuBoard


C++ Primer Plus
C Primer Plus (5th Edition)
ISBN: 0672326965
EAN: 2147483647
Year: 2000
Pages: 314
Authors: Stephen Prata

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net