在C语言中如何确定文件大小?

169

如何确定文件的大小,以字节为单位?

#include <stdio.h>

unsigned int fsize(char* file){
  //what goes here?
}

你需要使用一个库函数来检索文件的详细信息。由于C语言是完全平台无关的,所以你需要告诉我们你正在开发哪个平台/操作系统! - Chris Roberts
1
为什么使用 char* file,而不是 FILE* file?-1 - user12211554
@user12211554,所以你只需要使用strlen函数! - user26742873
1
请注意:文件大小可能会在“fsize”和“read”之间增长。请小心。 - user26742873
15个回答

176
在类Unix系统中,您可以使用POSIX系统调用:stat获取路径的信息fstat获取已打开文件描述符的信息(参见POSIX手册页,Linux手册页)。 (通过open(2)获取文件描述符,或者在stdio流中使用fileno(FILE*))。基于NilObject的代码:
#include <sys/stat.h>
#include <sys/types.h>

off_t fsize(const char *filename) {
    struct stat st; 

    if (stat(filename, &st) == 0)
        return st.st_size;

    return -1; 
}

变更:

  • 将文件名参数改为const char
  • 更正了struct stat的定义,该定义缺少变量名。
  • 出现错误时返回-1而不是0,对于空文件来说使用0会有歧义。由于off_t是有符号类型,因此这是可能的。

如果您想使fsize()在出现错误时打印一条消息,可以使用以下内容:

#include <sys/stat.h>
#include <sys/types.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>

off_t fsize(const char *filename) {
    struct stat st;

    if (stat(filename, &st) == 0)
        return st.st_size;

    fprintf(stderr, "Cannot determine size of %s: %s\n",
            filename, strerror(errno));

    return -1;
}

在32位系统中,您应该使用选项-D_FILE_OFFSET_BITS=64编译此内容,否则off_t只能容纳最多2 GB的值。有关详细信息,请参阅Linux中的大文件支持中的“使用LFS”部分。


22
这段内容适用于Linux/Unix系统,最好指出来,因为问题没有指定操作系统。 - Drew Hall
1
你可以将返回类型更改为ssize_t,并且可以轻松地从off_t转换大小。使用ssize_t似乎更有意义 :-) (不要与无符号的size_t混淆,它不能用于指示错误。) - T Percival
1
为了编写更具可移植性的代码,请使用 Derek 提出的 fseek + ftell - Ciro Santilli OurBigBook.com
14
为了让代码更易于移植,请使用Derek提出的fseekftell。不行。C标准明确规定,在二进制文件上使用fseek()SEEK_END是未定义的行为。 7.19.9.2 fseek函数 ...二进制流可能不支持具有SEEK_END值的whence参数的fseek调用,正如下面所注释的,这是链接C标准的第267页上脚注234中所述,并且明确将在二进制流中的fseekSEEK_END标记为未定义的行为。 - Andrew Henle
1
gnu libc手册:... [非POSIX]系统区分包含文本和包含二进制数据的文件,并且ISO C的输入和输出设施提供了这种区分。 ... 在GNU C库中,以及所有POSIX系统上,文本流和二进制流之间没有区别。当您打开流时,无论您请求二进制还是文本,您都会获得相同类型的流。此流可以处理任何文件内容,并且没有文本流有时具有的任何限制。 - Small Boy

82

不要使用 int。现在2GB以上的文件很常见。

不要使用 unsigned int。现在4GB以上的文件也很普遍。

据我所知,标准库将 off_t 定义为无符号64位整数,这就是每个人都应该使用的类型。当我们开始处理16 exabyte文件时,我们可以在未来几年内重新定义它为128位。

如果您在Windows上,则应使用GetFileSizeEx - 它实际上使用了有符号的64位整数,因此他们会在处理8 exabyte文件时遇到问题。愚蠢的微软! :-)


3
我曾使用过一些编译器,其中off_t是32位的。当然,这是在嵌入式系统中,4GB文件很少见。无论如何,POSIX也定义了off64_t和相应的方法,以增加混淆。 - Aaron Campbell
1
我总是喜欢那些假定 Windows 系统并且只会批评问题的回答。你能否请加入一些符合 POSIX 标准的内容呢? - S.S. Anne
2
@JL2210,Ted Percival的被采纳答案展示了一个符合posix标准的解决方案,所以我认为没有必要重复显而易见的内容。我(和其他70个人)认为,在此基础上添加有关Windows和不使用带符号32位整数表示文件大小的注意事项是一种增值。祝好! - Orion Edwards

34

马特的解决方案应该有效,除了它是C++而不是C,并且初始的tell应该不是必要的。

unsigned long fsize(char* file)
{
    FILE * f = fopen(file, "r");
    fseek(f, 0, SEEK_END);
    unsigned long len = (unsigned long)ftell(f);
    fclose(f);
    return len;
}

我为你修复了括号, ;)

更新:这并不是最佳解决方案。在Windows上仅限于4GB文件,并且使用类似GetFileSizeExstat64的特定于平台的调用可能比它更慢。


是的,你应该这样做。然而,除非确实有一个非常强有力的理由不写平台特定的代码,否则你应该只是使用特定于平台的调用,而不是采用打开/寻找-结束/告诉/关闭的模式。 - Derek Park
1
抱歉回复晚了,但我这里有一个重大问题。当访问受限文件(如密码保护或系统文件)时,它会导致应用程序挂起。是否有办法在需要时向用户请求密码? - Justin
@Justin,你可能需要开一个新的问题,专门讨论你遇到的问题,并提供关于你所在平台、如何访问文件以及行为的详细信息。 - Derek Park
5
C99和C11都从ftell()返回long int(unsigned long)强制转换无法扩展范围,因为其已经被该函数限制。ftell()在出错时返回-1,并且使用强制转换会使错误更加难以理解。建议fsize()返回与ftell()相同的类型。 - chux - Reinstate Monica
我同意。强制转换应该与问题中的原型匹配。我不记得为什么将它转换为unsigned long而不是unsigned int了。 - Derek Park
1
显然,您不会想使用 int,即使在 long 是 64 位类型的 64 位系统上,它也无法处理大文件。(例如,大多数非 Windows 64 位系统使用 LP64 ABI)。但实际上,您应该使用返回 off_tftello,在每个支持大文件的系统上都是 64 位的。 - Peter Cordes

16

不要这样做(为什么?):

引用我在网上找到的C99标准文档:“将文件位置指示器设置为文件结尾,如使用fseek(file,0,SEEK_END),对于二进制流(由于可能存在尾随的空字符)或具有状态相关编码但保证不以初始移位状态结束的任何流来说,都具有未定义的行为。”

更改定义为int,以便错误消息可以传递,然后使用fseek()ftell()来确定文件大小。

int fsize(char* file) {
  int size;
  FILE* fh;

  fh = fopen(file, "rb"); //binary mode
  if(fh != NULL){
    if( fseek(fh, 0, SEEK_END) ){
      fclose(fh);
      return -1;
    }

    size = ftell(fh);
    fclose(fh);
    return size;
  }

  return -1; //error
}

6
@mezhaka说道:那份CERT报告是完全错误的。如果你需要确定一个文件的长度,fseekoftello(或者如果你没有前一种方式并且可以接受对能够处理的文件大小设置限制,可以使用fseekftell)是正确的方法。基于stat的解决方案不能在许多“文件”(例如块设备)上工作,并且不能移植到非类POSIX系统。 - R.. GitHub STOP HELPING ICE
2
这是许多非posix兼容系统(例如我非常简约的mbed)获取文件大小的唯一方法。 - Earlz
1
你绝对不想在这里使用 intftell 返回一个带符号的 long,在许多(但不是所有)64位系统上是64位类型。在大多数32位系统上仍然只有32位,因此您需要使用 off_tftello 来能够便携地处理大文件。尽管 ISO C 选择不定义行为,但大多数实现确实如此,在大多数系统上实际上可以工作。 - Peter Cordes

12

POSIX

POSIX标准有自己的方法来获取文件大小。
使用sys/stat.h头文件来调用该函数。

概要

  • 使用stat(3)获取文件统计信息。
  • 获取st_size属性。

示例

注意:它将文件大小限制为4GB。如果不是Fat32文件系统,则使用64位版本!

#include <stdio.h>
#include <sys/stat.h>

int main(int argc, char** argv)
{
    struct stat info;
    stat(argv[1], &info);

    // 'st' is an acronym of 'stat'
    printf("%s: size=%ld\n", argv[1], info.st_size);
}

#include <stdio.h>
#include <sys/stat.h>

int main(int argc, char** argv)
{
    struct stat64 info;
    stat64(argv[1], &info);

    // 'st' is an acronym of 'stat'
    printf("%s: size=%ld\n", argv[1], info.st_size);
}

ANSI C(标准)

ANSI C 并没有直接提供确定文件长度的方法。
我们需要动动脑筋。现在,我们将使用寻找的方法!

概要

  • 使用 fseek(3) 将文件定位到末尾。
  • 使用 ftell(3) 获取当前位置。

示例

#include <stdio.h>

int main(int argc, char** argv)
{
    FILE* fp = fopen(argv[1]);
    int f_size;

    fseek(fp, 0, SEEK_END);
    f_size = ftell(fp);
    rewind(fp); // to back to start again

    printf("%s: size=%ld", (unsigned long)f_size);
}

如果文件是stdin或管道,POSIX,ANSI C将无法工作。 它将返回0,如果文件是管道或stdin观点: 您应该使用POSIX标准。因为它支持64位。

2
在Windows中,struct _stat64__stat64()用于编程。 - Bob Stein
1
最后一个例子是不正确的,fopen需要两个参数。 - M.M
2
在 ISO C 中,ftell 函数只能保证在以二进制模式打开文件时返回从文件开头到当前位置的字节数。然而,在文本模式下,ftell 返回的值是未指定的,并且仅对 fseek 有意义。 - Andreas Wenzel

4
如果您愿意使用标准C库:

#include <sys/stat.h>
off_t fsize(char *file) {
    struct stat filestat;
    if (stat(file, &filestat) == 0) {
        return filestat.st_size;
    }
    return 0;
}

26
这不是标准的C语言。它属于POSIX标准的一部分,但并非C标准。 - Derek Park

4

如果您正在构建Windows应用程序,请使用GetFileSizeEx API,因为CRT文件I/O在确定文件长度方面很混乱,这是由于不同系统上文件表示的特殊性造成的。 ;)


3

我使用以下代码来查找文件长度。

//opens a file with a file descriptor
FILE * i_file;
i_file = fopen(source, "r");

//gets a long from the file descriptor for fstat
long f_d = fileno(i_file);
struct stat buffer;
fstat(f_d, &buffer);

//stores file size
long file_length = buffer.st_size;
fclose(i_file);

这个解决方案使用特定于平台的函数。它很可能在非POSIX平台上无法工作。如果您对一个与平台无关的问题提供了特定于平台的答案,那么我建议您明确标记。 - Andreas Wenzel

3
我发现了fseek和ftell的方法以及一个相关问题的线程,其中回答说在C语言中不能用其他方法实现。
你可以使用类似NSPR这样的可移植性库(Firefox所使用的库)。

1
在普通的ISO C中,只有一种确定文件大小的方法是保证有效的:从开始读取整个文件,直到遇到文件结尾。
然而,这样做效率非常低。如果你想要更高效的解决方案,那么你必须要么依赖于特定平台的行为,要么回归到特定于平台的函数,例如Linux上的stat或Microsoft Windows上的GetFileSize
与其他答案所建议的相反,以下代码不能保证有效:
fseek( fp, 0, SEEK_END );
long size = ftell( fp );

即使我们假设数据类型long足够大以表示文件大小(这在某些平台上是值得怀疑的,尤其是微软Windows),发布的代码存在以下问题:
发布的代码不能保证在文本流上工作,因为根据ISO C11标准的§7.21.9.4 ¶2,由ftell返回的文件位置指示器的值包含未指定的信息。仅对于二进制流,此值保证是从文件开头算起的字符数。对于文本流没有这样的保证。
发布的代码也不能保证在二进制流上工作,因为根据ISO C11标准的§7.21.9.2 ¶3,二进制流不需要有意义地支持SEEK_END
话虽如此,在大多数常见平台上,如果我们假设数据类型long足够大以表示文件大小,则发布的代码将有效。
然而,在Microsoft Windows上,字符\r\n(回车后跟换行)将被翻译为\n用于文本流(但不适用于二进制流),因此您得到的文件大小将计算\r\n为两个字节,尽管您在文本模式下只读取了一个字符(\n)。因此,您得到的结果将不一致。
在基于POSIX的平台(例如Linux)上,这不是问题,因为在这些平台上,文本模式和二进制模式之间没有区别。

1
又一个Windows问题:在Windows上,long只有4个字节,这意味着对于大于2GB的文件,ftell()将会失败。 - Andrew Henle
@AndrewHenle:是的,那是一个重要的观点。同时,我已经编辑了我的答案。我相信现在我已经在我的答案中解决了您的问题。 - Andreas Wenzel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接