ftell(FILE*fd)和lseek(int fd,off_t offset,int whence)的结果之间的差异

Mic*_*iak 2 c glibc libc

考虑这个代码示例:

#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;
}
Run Code Online (Sandbox Code Playgroud)

该计划的结果令人惊讶:

ftell: 0
lseek: 14
A
Run Code Online (Sandbox Code Playgroud)

我正在尝试修复我的应用程序中的错误,但我确定前一段时间此程序的结果应该是0, 0(换句话说,从来没有在我的应用程序中出现此错误).

libc改变了这种奇怪的情况发生了什么?

我怎么能以良好的方式解决这个问题?

lseek(fileno(file), 0, SEEK_SET)一个好的解决方案?

Jon*_*art 6

这是危险的同时使用标准库的文件操作(例如fread(3),fseek(3)具有低级别的系统调用(例如,沿着)read(2),lseek(3)).

这是有问题的原因是因为标准库将缓冲事物,而不是立即将它们写入(到文件描述符)(取决于缓冲模式).

如果您需要访问基础文件描述符,您应该fflush在获取之前确保该流fileno.我在头文件中抛出了这样的东西:

/**
 * 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);
}
Run Code Online (Sandbox Code Playgroud)

此外,一旦你调用它,你可能不应该FILE*再次使用,因为你已经改变了内核文件指针,但标准库可能会认为它没有改变(因为你上次fflush编辑它).正如评论中提到,您可以FILE*像这样重新同步文件描述符:

/**
 * 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);
}
Run Code Online (Sandbox Code Playgroud)