At the very beginning of this chapter, the two file types were mentioned: plain text (formally called ASCII) and binary. All of the previous examples have used plain-text files, which are human readable, but you can also write to and read from binary files using C. The basic ideas for working with binary files are the same, but the specific functions are different.
Binary files are opened and closed like ASCII files, except that you should use the b modifier on the mode:
FILE *fp; fp = fopen ("thefile", "wb"); // Write to the file. fclose(fp);
Once you've successfully opened the file, you can write data to it using fwrite(). Its syntax is more complex than you've seen so far:
fwrite (write_data_pointer, bytes_to_write, blocks, fp);
The first argument to fwrite() is a pointer to the address of the data being written. This can be an actual pointer (like to a string or a number) or an array (which, as you've seen, has pointer-like qualities). The second argument indicates the size, in bytes, of each block of data. The third argument represents the number of blocks being written. For example, you might write one block, which is a number, or one hundred blocks, which are the elements of an array. The final argument is the file pointer.
In this next example, an array of 50 random numbers will be generated (see the sidebar) and then written to a binary file. Subsequent examples will read the entire file and grab random values from it.
1. | Create a new file or project in your text editor or IDE. |
2. | Type the standard beginning lines of code (Script 12.5): /* binary_write.c - Script 12.5 */ #include <stdio.h> |
3. | Require two more standard libraries: #include <stdlib.h> #include <time.h> The first library makes the rand() and srand() functions available to this application. The second makes the time() function available, which will be needed with srand(). See the sidebar for more. |
4. | Set the file path and name as a C preprocessor macro: #define THEFILE "/Users/larry/Desktop/numbers.dat" or #define THEFILE "C:\Documents and Settings\Larry Ullman\Desktop\ numbers.dat" Because the file will store binary data, not plain text, you don't want to use the .txt extension. You can either omit the extension entirely or use .dat (a three-letter extension representing data). |
5. | Create a constant macro representing the number of items in the array: #define ITEMS 50 How many items are stored in the array will be a relevant number many times over in this application, so it makes sense to set it as a preprocessor macro. |
6. | Begin the main function and create a file pointer: int main (void) { FILE *fp; |
7. | Define the required variables: int i; int numbers[ITEMS]; The i variable will be a loop counter; numbers is the array in which random numbers will be stored. |
8. | Open the file for binary writing: fp = fopen(THEFILE, "wb"); This call opens the file for writing, creating the file if it doesn't exist, and wiping out any existing data. The binary mode is indicated by adding the b. This is only required on Windows but is still a good idea on all operating systems. |
9. | Start a conditional based on the file pointer: if (fp != NULL) { |
10. | Seed the rand() function: srand((unsigned)time(NULL)); This line is required to generate truly random numbers. See the sidebar for a more detailed explanation of the purpose and syntax. |
11. | Populate the array with random numbers: for (i = 0; i < ITEMS; i++) { numbers[i] = rand() % 100; } The for loop counts from i to ITEMS, the number of elements in the array. Within the loop, each element is assigned a random value between 0 and 99. This limit is accomplished by assigning the remainder of dividing the random number by 100, rather than assigning the random number itself (which could be as high as 32,767). |
12. | Write the array elements to the binary file and print a message to the user: fwrite (numbers, sizeof(int), ITEMS, fp); printf ("The data has been written. "); The fwrite() line starts by using the numbers variable as its pointer. This works because an array name is equivalent to its address in C (see Chapter 9, “Working with Pointers”). The function is then told to write ITEMS number of blocks, each of which is sizeof(int) bytes in size. We use sizeof(int) because numbers is an array of integers. In layman's terms, the fwrite() line could be described as: go to the memory block, which starts where the numbers array starts, then take the next 50 blocks of data (each block being 4 bytes—the common size of an integer—in length) and write these to the file referenced by fp. The printf() line just gives you something to see when the application runs. You could also print out all of the randomly generated numbers, if you want. |
13. | Complete the fp conditional: } else { printf ("The file could not be opened. "); return 1; } |
14. | Close the file: if (fclose(fp) != 0) { printf ("The file could not be closed. "); } |
15. | |
16. | Save the file as binary_write.c, compile, and debug as necessary. |
17. | Run the application (Figure 12.10). Figure 12.10. The results of successfully writing 50 random numbers to a binary data file. |
18. | Open numbers.dat in a text editor (Figure 12.11). Figure 12.11. The illegible binary contents of the numbers.dat file. |
✓ Tips
Whereas the fprintf() function writes text to a text file, the fwrite() function copies data stored in memory—which is always binary—to a file. This is why the first argument is a pointer (the address of a memory block), not a simple variable.
If you inadvertently open an ASCII file as binary on Windows, C will not handle the line endings properly, meaning you'll need to cope with them in your C code.
In case you were curious, ASCII stands for American Standard Code for Information Interchange.
Instead of using a loop to populate the array and then writing the entire array to the file, this application could write the random number directly to the binary file inside of the loop. In such a case you would change your fwrite() line so that it writes a single block of sizeof(int) size to the file at a time, rather than ITEMS number of blocks of sizeof(int) data. In other words, instead of creating an array of, say, 50 elements and then writing 50 blocks of data in one fwrite() call, you would just write a single block of data 50 times.