原因 1
找到真正的文件位置开始。
由于 stdio 的缓冲区实现,stdio 流位置可能与 OS 文件位置不同。当您读取 1 个字节时,stdio 将文件位置标记为 1。由于缓冲,stdio 可能会从底层文件中读取 4096 个字节,其中 OS 会将其文件位置记录为 4096。当您切换到输出时,您确实需要选择您想使用哪个位置。
原因 2
找到正确的缓冲区光标开始。
tl;博士,
如果底层实现只使用单个共享缓冲区进行读写,则在更改 IO 方向时必须刷新缓冲区。
以这个在 chromium os 中使用的glibc 来演示 fwrite、fseek 和 fflush 如何处理单个共享缓冲区。
fwrite填充缓冲区实现:
fill_buffer:
while (to_write > 0)
{
register size_t n = to_write;
if (n > buffer_space)
n = buffer_space;
buffer_space -= n;
written += n;
to_write -= n;
if (n < 20)
while (n-- > 0)
*stream->__bufp++ = *p++;
else
{
memcpy ((void *) stream->__bufp, (void *) p, n);
stream->__bufp += n;
p += n;
}
if (to_write == 0)
/* Done writing. */
break;
else if (buffer_space == 0)
{
/* We have filled the buffer, so flush it. */
if (fflush (stream) == EOF)
break;
从这段代码sn-p我们可以看出,如果缓冲区满了,就会刷新它。
我们来看看fflush
int
fflush (stream)
register FILE *stream;
{
if (stream == NULL) {...}
if (!__validfp (stream) || !stream->__mode.__write)
{
__set_errno (EINVAL);
return EOF;
}
return __flshfp (stream, EOF);
}
它使用__flshfp
/* Flush the buffer for FP and also write C if FLUSH_ONLY is nonzero.
This is the function used by putc and fflush. */
int
__flshfp (fp, c)
register FILE *fp;
int c;
{
/* Make room in the buffer. */
(*fp->__room_funcs.__output) (fp, flush_only ? EOF : (unsigned char) c);
}
__room_funcs.__output 默认使用flushbuf
/* Write out the buffered data. */
wrote = (*fp->__io_funcs.__write) (fp->__cookie, fp->__buffer,
to_write);
现在我们很接近了。什么是__写?追踪前面提到的默认设置,它是__stdio_write
int
__stdio_write (cookie, buf, n)
void *cookie;
register const char *buf;
register size_t n;
{
const int fd = (int) cookie;
register size_t written = 0;
while (n > 0)
{
int count = __write (fd, buf, (int) n);
if (count > 0)
{
buf += count;
written += count;
n -= count;
}
else if (count < 0
#if defined (EINTR) && defined (EINTR_REPEAT)
&& errno != EINTR
#endif
)
/* Write error. */
return -1;
}
return (int) written;
}
__write 是对write(3) 的系统调用。
正如我们所见,fwrite 仅使用一个缓冲区。如果改变方向,它仍然可以存储之前的写入内容。从上面的例子中,你可以调用fflush清空缓冲区。
同样适用于fseek
/* Move the file position of STREAM to OFFSET
bytes from the beginning of the file if WHENCE
is SEEK_SET, the end of the file is it is SEEK_END,
or the current position if it is SEEK_CUR. */
int
fseek (stream, offset, whence)
register FILE *stream;
long int offset;
int whence;
{
...
if (stream->__mode.__write && __flshfp (stream, EOF) == EOF)
return EOF;
...
/* O is now an absolute position, the new target. */
stream->__target = o;
/* Set bufp and both end pointers to the beginning of the buffer.
The next i/o will force a call to the input/output room function. */
stream->__bufp
= stream->__get_limit = stream->__put_limit = stream->__buffer;
...
}
它会在最后软刷新(重置)缓冲区,这意味着读取缓冲区将在此调用后清空。
这符合 C99 的基本原理:
只有在 fsetpos、fseek、rewind 或 fflush 操作成功后才允许更改更新文件的输入/输出方向,因为这些正是确保 I/O 缓冲区已被刷新的函数。