ruanhao
ruanhao

Reputation: 4922

I/O between input and output in C programme

I saw a paragraph on APUE (chapter 5.5):

When a file is opened for reading and writing, the following restrictions apply:
(1) Output cannot be directly followed by input without an intervening fflush, fseek, fsetpos,or rewind.
(2) Input cannot be directly followed by output without an intervening fseek, fsetpos,or rewind, or an input operation that encounters an end of file.

I can imagine why we should use fseek, fsetpos. But why I should use fflush in case (1) and rewind in case (2).
If we use fflush in case (1), there is nothing left to read. And if we use rewind in case (2), something to be ouputed will overlap with original content. Am I right?

I have tried 2 experiments:

int main() {
    FILE *file_p = fopen("new.txt", "r+");
    char buf[1024];
    char *str;

    fputs("hongkong\n", file_p);
    fputs("shanghai\n", file_p);

    fflush(file_p);

    fputs(fgets(buf, 1024, file_p), stderr);

    fclose(file_p);
    return 0;
}
/* Segmentation fault */

int main() {
    FILE *file_p = fopen("new.txt", "r+");
    char buf[1024];
    char *str;

    fgets(buf, 1024, file_p);
    rewind(file_p);
    fputs("hongkong\n", file_p);

    fclose(file_p);
    return 0;
}
/* origin text in new.txt is:
   shanghai
   taipei
   after execution:
   hongkong
   taipei
*/

Is there something I misunderstood with this paragraph?

Upvotes: 1

Views: 247

Answers (3)

Eric Z
Eric Z

Reputation: 14515

Those two rules are originated from C standard. Excerpt from C99:

When a file is opened with update mode ('+' as the second or third character in the above list of mode argument values), both input and output may be performed on the associated stream. However, output shall not be directly followed by input without an intervening call to the fflush function or to a file positioning function (fseek, fsetpos, or rewind), and input shall not be directly followed by output without an intervening call to a file positioning function, unless the input operation encounters endof- file.

The background knowledge is that there is usually some kind of I/O buffering mechanism employed by standard I/O library you use (e.g., MSVC CRT, libc) to improve I/O performance. The basic idea is that when there is something to write, it's pushed onto an internal buffer first. The buffer gets flushed to disk only when it's filled. For read, it reads one block a time to the buffer even if what you ask is just few bytes. This is nice because you avoid multiple system calls (e.g., read(), write()) which has its own overhead. In practice, this saves time due to program locality. When you read some bytes, you are likely to read the next few bytes sooner. The same holds for write.

The easiest way to implement this buffering is to attach a single buffer for each file. This buffer can be used for either read or write, but not at the same time (sounds familiar? => half-duplex in networks).

Back to the two rules. In a read-after-write scenario, you need to fflush the buffer or call some file positioning function. This is because if you don't do that, the written data in the buffer may get lost. Why do file positioning functions work? Because it can possibly cause a flush and reset the buffer state so that it can be reused for read or write later (a reposition is basically changing the file position pointer to refer to another block in the file. Thus the previous block in the buffer, if any, should semantically be "committed" or "reverted", right?). It also explains rule 2.

Keeping that in mind, your following code is not portable:

fputs(fgets(buf, 1024, file_p), stderr);

Although you may not encounter any problem (except the segmentation fault as shown below) on Linux as stated explicitly on fopen's man page, you may see problems on other platforms, such as Windows (MSVC CRT do have such "problems", but still standard-conformant).

You need call some positioning function between the call to fgets() and fputs(). It's ok to perform a no-op seek:

fseek(..., 0L, SEEK_CUR);

Last but not least, as others have pointed out, fgets() may return NULL in case of EOF which is not acceptable to fputs(), which causes the segmentation fault.

Upvotes: 0

shashank
shashank

Reputation: 410

Case-1

fflush() is used to transfer the data in the intermediate buffer to the file although the buffer is not full(usually data is transmitted after buffer is full).

Case-2

After the Output the File Pointer will be in the last position i,e EOF so if you enter input immediately you will not be able to data as the file pointer is at EOF. So you need to set position using rewind() or fsetpos().

Regarding your experiments

Experiment-1

After fputs file pointer reaches EOF.So fgets encounters EOF so segmentation fault is displayed. Use rewind(). Your error will be rectified.

Experiment-2

You are using rewind() so file pointer goes to starting of file. so your input is replaced by entry in file.

Upvotes: 0

Abend
Abend

Reputation: 589

1) When you write data into a file, it does not go to the file directly, it goes to a buffer. When it gets filled finally it leaves the file written. All this is to improve performance. One of fflush() uses is for save the data from the buffer into the file regardless if this buffer is half filled. It is like a commit for DBs. For that reason, if you want to read a file, before you need to be sure that all data is there.

2) Remind that to fetch files exists a cursor that goes along the file. If you write a register of data into the file, the cursor will be in the last position of the file (or in the position + 1 where you were before), so if you read it after that, you will reach EOF (in case you were in the last register).

In your first example:

 fputs(fgets(buf, 1024, file_p), stderr);

fgets() reaches EOF, for that reason fputs() bring you a segmentation fault because it is receiving NULL.

In the second one you are overwriting the first line.

Upvotes: 1

Related Questions