C中的文件指针,如何测量当前位置到文件末尾的距离?

For a file pointer in C, how to measure the distance between the current position to the end of file?

对于给定的文件指针(FILE *),是否可以快速确定从当前位置到文件末尾的距离。

计算实际距离所需的时间应该与距离无关。

比如fpos_t的减法,但是fpos_t不是整数,不能进行数值运算。还有其他方法吗?

For example the subtraction of fpos_t, but fpos_t is not integer, it cannot be operated numerically. Is there any alternative way?

不,你不能。

FILE 结构不知道文件有多长,因此没有简单的方法来找到这个距离。该文件被视为一条道路 - 您转入道路并沿着道路行驶,当道路结束时,您会发现。但是没有标志告诉你路有多长。

您可以单独询问 OS,使用 stat 或类似的。但请注意,FILE 甚至并不总是引用具有已定义结尾的文件 - 它可能 stdin 来自管道,并且当时大小完全未知。

当你第一次打开文件时,你可以使用fseek()转到文件末尾(但请参阅下面的注释),然后使用ftell() 获取位置并保存(作为文件的大小)。然后调用rewind()回到开头。

然后,可以从保存的 'size' 中减去以后调用 ftell() 的 return 值,以获得从当前位置到文件末尾的偏移量(距离) :

// Given a FILE* fp that's just been opened:
fseek(fp, 0, SEEK_END);
long int endpos = ftell(fp);
rewind(fp); // Or you can use fseek(fp, 0, SEEK_SET);
//...
// Later in your code:
long int dtoend = endpos - ftell(pf);

但请注意,实现 不需要 来实现 SEEK_END:来自上面链接的 fseek 上的 cplusplus.com 页面:

  • Library implementations are allowed to not meaningfully support SEEK_END (therefore, code using it has no real standard portability).

澄清一下(根据一些评论):上面的代码 要求 endpos 值在文件 open/read 操作。一个 可以 通过寻找结束然后在任何时候恢复 当前 位置来避免这种情况,但效率会低得多。例如,可以编写一个函数来随时获取到终点的距离:

long int dtoend(FILE *fp)
{
    long int curpos = ftell(fp); // Get current position
    fseek(fp, 0, SEEK_END);      // Go to the file's end
    long endpos = ftell(fp);     // Gets the file's size
    fseek(fp, curpos, SEEK_SET); // Restore previous pos
    return endpos - curpos;      // Return the distance!
}


使用大型(> 2GB)文件的注意事项:以上代码使用标准 fseekftell 函数,它们使用 long int 类型(即通常为 32 位宽)用于文件位置;要对较大的文件使用类似的代码,有许多(尽管是特定于平台的)替代方案...

在 Windows 平台上,使用 MSVC(或兼容)编译器,有 _ftelli64 and _fseeki64 函数,它们的使用方式几乎与它们的 'standard'同行;例如,通过对上述代码进行以下更改:

//...
    int64_t curpos = _ftelli64(fp); // Get current position
    _fseeki64(fp, 0LL, SEEK_END);   // Go to the file's end
    //... and similar changes elsewhere

在 Linux 系统上,如果您 确保 ,则 64 位调用将实现为 ftello and fseeko#define _FILE_OFFSET_BITS 64.

其他 platforms/compilers 可能会实现上述任一(或两者),或者有一些其他非常相似的 64 位替代品。


注释 #2 - 错误处理: 正如评论中所指出的,使用 SEEK_END 作为 origin 参数调用 fseek可能会在多种不同情况下失败;例如,如果文件指针是 stdin,如果它指的是管道流,或者(在某些系统上)如果文件以 文本模式 [=78= 打开]†。要处理这种情况,应该真正检查 fseek 调用的 return 值,如果失败,它将是 非零 。因此,这里是实现了此类错误处理的 dtoend 函数的 64 位版本(请注意 MSVCGNU 以外的编译器,您需要添加相关的定义宏bigseekbigtell 函数):

#include <stdio.h>
#include <stdint.h>

#if defined (_MSC_VER) // MSVC/Windows...
    #define bigtell _ftelli64
    #define bigseek _fseeki64
#elif defined (__GNUC__) // GNU/Linux...
    #define _FILE_OFFSET_BITS 64
    #define bigtell ftello
    #define bigseek fseeko
//
// Feel free to add other compiler/platform implementations
//

#else // Unknown platform/compiler - likely to cause warnings!
    #define bigtell ftell
    #define bigseek fseek 
#endif

int64_t dtoend(FILE* fp)
{
    int64_t curpos = bigtell(fp);                   // Saves the file's current position
    if (bigseek(fp, 0LL, SEEK_END) != 0) return -1; // -1 can ONLY be an error condition
    int64_t endpos = bigtell(fp);                   // Retrieve file size (end position)
    bigseek(fp, curpos, SEEK_SET);                  // Restore previously saved position
    return endpos - curpos;                         // Subtract to get distance from end
}

来自上面链接的同一 cplusplus.com 页面:

For streams open in text mode, offset shall either be zero or a value returned by a previous call to ftell, and origin shall necessarily be SEEK_SET. If the function is called with other values for these arguments, support depends on the particular system and library implementation (non-portable).