The block I/O functions perform simple operations that require programmers to perform many additional data processing steps. Some of the required steps are based on operations covered previously. Please review the following as needed:
There are two ways we might think block I/O is simple. First, it is simple in the sense that the two main block I/O functions require only two parameters: an address and the number of bytes to transfer. The address is where the read
function stores the input data and from where the write
function takes the output data. Block I/O is also simple in the sense that neither function formats nor modifies the data in any way - the functions simply move data, as a stream of bytes, between the program and a file. Although the first argument for both functions is a character pointer, char*
, we can use the functions to read and write many kinds of data. The type char
is used here as a synonym for "byte."
ostream& write(char* buffer, streamsize nbytes);
istream& read(char* buffer, streamsize nbytes);
streamsize gcount();
int
, which is typically four or eight bytes). streamsize
is a pseudo data type that is translated into an unsigned integer whose size is appropriate for a given system. Repeating the description of gcount
from the Character I/O section, the "g" in the function name is short for "get," which is used as a synonym for read or input. The function returns a count of how many bytes or characters were read with the last read function call. For more detail, please see:
read
or write
function is called, the programmer must have some way of knowing how many bytes to read or write. There are three common ways of solving this problem:
Although block I/O is flexible, powerful, and the mainstay of some kinds of computing, there is a critical aspect we keep in mind: Data processed with block I/O functions are typically in a binary format and are often not portable between computers based on different hardware or running other operating systems. Even on the same computer, different compiler settings can make data written by one program unreadable by another. For example, the Visual Studio compiler generates 32-bit integers by default, but can we reconfigure it to create 64-bit integers. The differences between how various computers represent binary data often make it necessary to programmatically "massage" data when transferring it between systems. Alternatively, we can export the data to a more portable format - for example, the comma-separated values or CSV format used by many spreadsheet and database programs - and then import it on the second system. Fortunately, moving binary data between incompatible systems happens infrequently.
Although the first argument of both the read
and write
functions is a character pointer, the block I/O functions treat the data as essentially typeless. When the block I/O functions read or write data, they treat it as just a stream or sequence of bytes - the kind of data that the block represents is not significant. So, with typecasting, the block I/O functions can handle any data.
Data | Block I/O | |
---|---|---|
(a) | ifstream in(in_name, ios::binary); ofstream out(out_name, ios::binary); |
|
(b) |
char block[512]; int count; |
in.read(block, 512); while ((count = in.gcount()) > 0) { out.write(block, count); in.read(block, 512); } |
(c) | int counter; |
out.write((char *) & counter, sizeof(int)); in.read((char *) & counter, sizeof(int)); |
(d) | int array[100]; |
for (int i = 0; i < 100; i++) out.write((char *) & array[i], sizeof(int)); out.write((char *)array, 100 * sizeof(int)); for (int i = 0; i < 100; i++) in.read((char *) & array[i], sizeof(int)); in.read((char *)array, 100 * sizeof(int)); |
(e) | struct foo { . . . }; foo my_foo; |
out.write((char *) & my_foo, sizeof(foo)); in.read((char *) & my_foo, sizeof(foo)); |
(f) | struct foo { . . . }; foo my_foo[100]; |
for (int i = 0; i < 100; i++) out.write((char *) & my_foo[i], sizeof(foo)); out.write((char *) my_foo, 100 * sizeof(foo)); for (int i = 0; i < 100; i++) in.read((char *) & my_foo[i], sizeof(foo)); in.read((char *) my_foo, 100 * sizeof(foo)); |
read
function requests 512 bytes but will read less if there are fewer than 512 remaining in the file. The gcount
function returns the number of bytes read by the last read operation. This code fragment copies a file, so, to preserve the correct file size, the write
function writes the actual number of bytes read.int
with double
, float
, etc. for other kinds of fundamental or primitive data types.