What is the use of zero offset in fseek() function with SEEK_CUR?
Asked Answered
L

2

49
while (fread(&product, sizeof(Product), 1, file) == 1) {
        product.price *= 2.0;
        fseek(file, -sizeof(Product), SEEK_CUR);
        fwrite(&product, sizeof(Product), 1, file);
        fseek(file, 0, SEEK_CUR);
}

Using the code above, I tried to double the price of each product in the binary file, where each record is an instance of a Product struct. When I ran this code, it updated all the prices in the file correctly.

As far as I know, fread automatically moves the file pointer to the next record. Using the first fseek, the code moves the file pointer back to the beginning of the record. Then it updates it using fwrite. fwrite automatically moves the file pointer to the next record. In the while loop, fread should continue to read the next record, and so on.

It seems that fseek(file, 0, SEEK_CUR); is unnecessary here. However, if I remove it, the code enters an infinite loop. I cannot figure out why this happens.

I tried to observe the value of file pointer using ftell. But I did not see any change its value after fseek(file, 0, SEEK_CUR);.

Leeland answered 14/5, 2023 at 3:58 Comment(1)
Note: better code would check the return values of fwrite() and fseek().Quimby
A
67

When a stream is opened for both reading and writing, you are not allowed to directly switch between reading and writing. §7.21.5.3 ¶7 of the ISO C11 standard states the following:

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 end-of-file.

If you remove the line

fseek(file, 0, SEEK_CUR);

then your program violates this rule, which invokes undefined behavior. This means that anything can happen, which includes the possibility of your program getting stuck in an infinite loop.

Animalize answered 14/5, 2023 at 3:59 Comment(5)
Good answer. You might also mention that the posted code does not have defined behavior if the file is not opened in binary mode.Yellowweed
@chqrlie: Since OP stated that the file is binary, I assume that the file was either opened in binary mode or that OP is using a platform on which there is no difference between text mode and binary mode (such as POSIX/Linux). However, you are correct that according to §7.21.9.2 ¶4 of the ISO C11 standard, the line fseek(file, -sizeof(Product), SEEK_CUR); will invoke undefined behavior in text mode. On POSIX platforms (such as Linux), the behavior is defined, though.Animalize
Curious if fflush() can do it or not.Barbbarba
@chqrlie: Unless Product is a char[] he's in binary mode or he invoked undefined behavior at fwrite().Barbbarba
@Joshua: as stated in the answer, the call fseek(file, 0, SEEK_CUR); can be replaced with fflush(file); and flushing might be more reliable but also more costly.Yellowweed
S
7

Some file systems which are rather slow to perform random seeks are designed to allow efficiently process read-modify-write operations on a sequence of records by using separate read- and write pointers. A program such as yours might be able to omit the fseek operations entirely provided the amount of data read matched the amount of data written, and might run much faster without the fseek() operations than it would run if they were included.

A C Standard Library implementation would need to keep track of whether the last operation performed on a file was a write or a read, so that a relative fseek() could operate relative to either the the current read position or the current write position, depending upon which kind of operation was performed last, but could allow programs written to exploit dual file pointers to reap the associated performance gains when targeting systems that use them.

Although the Standard could have categorized the behavior of doing a write and read without an intervening fseek() as Implementation-Defined, that would have been seen as implying that implementations should try to behave in a consistent documented fashion even when targeting platforms with corner-case behaviors that aren't always predictable.

Seavir answered 15/5, 2023 at 19:50 Comment(2)
I was going to write this answer! The OP's reported behaviour of looping when the second fseek() is omitted is consistent with his runtime having separate read and write pointers which are synchronised by fseek().Endocardium
@grahamj42: It's important to note that the Standrad was intended to give programmers a "fighting chance" to write portable programs, not to ensure that programmers could achieve the same level of performance with portable code as would be available using non-portable constructs. If a system is designed to maximize the performance of read-modify-write pattern without fseek, and isn't designed to optimize fseek performance, using two fseeks per record might degrade performance by an order of magnitude.Seavir

© 2022 - 2024 — McMap. All rights reserved.