考虑这个代码示例:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
//this file exists and contains data: "ABCDEFGHIJKLM"
FILE* file = fopen("file.txt", "r");
char data[4];
long int pos = ftell(file);
fseek(file, 0, SEEK_SET);
fread(data, 4, 1, file);
fseek(file, pos, SEEK_SET);
printf("ftell: %d\n", ftell(file));
printf("lseek: %d\n", lseek(fileno(file), 0, SEEK_CUR));
fread(data, 1, 4, file);
//this correctly prints A
//but external function needs fileno(file) that has wrong pos
printf("%c\n", data[0]);
fclose(file);
return 0;
}
该计划的结果令人惊讶:
ftell: 0
lseek: 14
A
我试图修复我的应用程序中的错误,但我确定前一段时间此程序的结果应该是0, 0
(换句话说,从来没有出现过此错误在我的应用程序中)。
libc改变了这种奇怪的情况发生了什么?
我如何以良好的方式解决这个问题?
lseek(fileno(file), 0, SEEK_SET)
是一个很好的解决方案吗?
答案 0 :(得分:6)
It is dangerous to use both standard library file operations (e.g. fread(3)
, fseek(3)
) along with low-level system calls (e.g. read(2)
, lseek(3)
).
The reason this is problematic is because the standard library will buffer things, and not actually write them out (to the file descriptor) immediately (depending on the buffering mode).
If you need to access the underlying file descriptor, you should be sure to fflush
the stream before getting its fileno
. I've thrown something like this in a header file:
/**
* Safely get the file descriptor associated with FILE,
* by fflush()ing its contents first.
*/
static inline int safe_fileno(FILE *f)
{
fflush(f);
return fileno(f);
}
Also, once you call this, you probably shouldn't go back to using the FILE*
again, because you've changed the kernel file pointer, but the standard library may assume it is unchanged (since you last fflush
ed it). As was mentioned in a comment, you may be able to re-sync the FILE*
with the file descriptor like this:
/**
* Re-synchronize the file offset of a FILE with the
* file offset of its underlying file descriptor.
*/
static inline void fresync(FILE *f)
{
off_t off = lseek(fileno(f), 0, SEEK_CUR);
fseek(f, off, SEEK_SET);
}