如何在C语言中获取文件的大小?

466

我如何在使用 C 编写的应用程序中找出已打开文件的大小?

我想要知道文件大小,因为我想将加载的文件内容放入一个字符串中,该字符串是使用 malloc() 分配的。

只写 malloc(10000*sizeof(char)); 是不好的做法。


46
请注意,按照定义,char类型的大小为1。 - Randy Proctor
15
是的,但是某些晦涩难懂的平台编译器可能会将char定义为2个字节,这样程序会分配比必要更多的内存。我们永远不能太确定。 - Nathan Osman
38
@George,如果一个“神秘平台的编译器”中sizeof(char) != 1,则它不是真正的C编译器。即使字符是32位,它仍将返回1。 - Andrew Flanagan
26
C(和C++)标准保证sizeof(char)==1。参考http://www.parashift.com/c++-faq-lite/intrinsic-types.html#faq-26.1。 - sleske
58
在分配 x 个字符时,我更喜欢使用 malloc(x*sizeof(char)); 而不是 malloc(x);。尽管它们编译后的结果相同,但我喜欢与其他内存分配保持一致性。 - moltenform
显示剩余4条评论
8个回答

634

您需要寻找文件结尾,然后请求该位置:

fseek(fp, 0L, SEEK_END);
sz = ftell(fp);

然后您可以进行回溯,例如:

fseek(fp, 0L, SEEK_SET);

或者(如果寻求回到开头)

rewind(fp);

13
谢谢。这个评论解决了我在文件大小算法方面的问题。记录一下,当我们在fopen的模式字符串末尾加上“b”时,可以使用二进制模式打开文件。 - T.E.D.
71
大意:嘿,人们快忘记 rewind 的意思了,使用 rewind 来重新回到起点。翻译:在人们忘记其含义之前,使用 rewind 将其倒回到起点。 - bobobobo
134
返回一个有符号整数,因此限制为2 GB。但好的一面是,你的文件可以长达负20亿字节,他们已经为此做好了准备。 - Seth
27
length = lseek(fd, 0, SEEK_END)+1; - Volodymyr M. Lisivka
30
根据fseek文档,“库的实现可以选择不支持SEEK_END(因此,使用它的代码没有真正的标准可移植性)。” - Mika Haarahiltunen
显示剩余17条评论

455

使用标准库:

假设您的实现有意义地支持SEEK_END:

fseek(f, 0, SEEK_END); // seek to end of file
size = ftell(f); // get current file pointer
fseek(f, 0, SEEK_SET); // seek back to beginning of file
// proceed with allocating memory and reading the file

Linux/POSIX:

如果你知道文件名,可以使用 stat 函数;如果你有文件描述符,则可以使用 fstat 函数。

以下是 stat 函数的示例:

#include <sys/stat.h>
struct stat st;
stat(filename, &st);
size = st.st_size;

Win32:

您可以使用GetFileSizeGetFileSizeEx函数。


21
请注意,出于清晰度的考虑,我已省略了错误检查。 - Greg Hewgill
21
不需要文件名,可以使用 fstat 进行操作。 - Tanktalus
4
你需要指定结构体的地址。第二行应该是:stat(filename, &st); - Vlad the Impala
12
为了-FATAL ERROR,退出,我省略了错误检查。 - Buttle Butkus
12
第二个选项是唯一一个可以显示大于2GB文件大小的选项。 - Seth
显示剩余10条评论

131

如果您拥有文件描述符,则fstat()返回一个stat结构,其中包含文件大小。

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

// fd = fileno(f); //if you have a stream (e.g. from fopen), not a file descriptor.
struct stat buf;
fstat(fd, &buf);
off_t size = buf.st_size;

3
如果您有一个流(比如来自于fopen),而不是文件描述符,请添加 "fd = fileno(f);"。需要进行错误检查。 - ysth
18
当然需要错误检查 - 否则会让这个示例变得更加复杂。 - PiedPiper
6
在我看来,这是最好的真实答案。我认为我们大多数人在 C 语言方面已经摆脱了初学者的阶段,难道我们真的需要在示例中添加错误检查和其他不必要的代码吗?M$DN 在他们的示例中已经做得够糟糕了,让我们不要效仿他们,而是在结尾处简单地说一句“确保添加错误检查”,然后就可以了。 - osirisgothra
18
SO的很多用户是C语言的学生,而不是过去的专家。因此,答案中给出的代码应该展示错误检查,让学生学习编码的正确方式。 - user3629249
5
有一个细节需要注意,即(f)stat()返回块分配的总字节数,而fseek()/ftell()序列返回在遇到EOF之前的字节数。 - user3629249
显示剩余2条评论

27

最终,我只编写了一个简短而简洁的fsize函数(注意,没有错误检查)。

int fsize(FILE *fp){
    int prev=ftell(fp);
    fseek(fp, 0L, SEEK_END);
    int sz=ftell(fp);
    fseek(fp,prev,SEEK_SET); //go back to where we were
    return sz;
}

标准C库没有这样的函数有点傻,但我可以理解为什么这很困难,因为并非每个“文件”都有一个大小(例如/dev/null)。


3
恢复文件流先前位置指示器是一个好点子。 - Fredrick Gauss
2
ftell(fp) 返回 long。不需要将其缩短为 int 以避免信息丢失。 - chux - Reinstate Monica
对于在这里看到的任何人来说,你不需要使用ftell,lseek通过减少一个系统调用返回当前位置。 - Shahaboddin

19

如何使用 lseek/fseek/stat/fstat 获取文件大小?

#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>

void
fseek_filesize(const char *filename)
{
    FILE *fp = NULL;
    long off;

    fp = fopen(filename, "r");
    if (fp == NULL)
    {
        printf("failed to fopen %s\n", filename);
        exit(EXIT_FAILURE);
    }

    if (fseek(fp, 0, SEEK_END) == -1)
    {
        printf("failed to fseek %s\n", filename);
        exit(EXIT_FAILURE);
    }

    off = ftell(fp);
    if (off == -1)
    {
        printf("failed to ftell %s\n", filename);
        exit(EXIT_FAILURE);
    }

    printf("[*] fseek_filesize - file: %s, size: %ld\n", filename, off);

    if (fclose(fp) != 0)
    {
        printf("failed to fclose %s\n", filename);
        exit(EXIT_FAILURE);
    }
}

void
fstat_filesize(const char *filename)
{
    int fd;
    struct stat statbuf;

    fd = open(filename, O_RDONLY, S_IRUSR | S_IRGRP);
    if (fd == -1)
    {
        printf("failed to open %s\n", filename);
        exit(EXIT_FAILURE);
    }

    if (fstat(fd, &statbuf) == -1)
    {
        printf("failed to fstat %s\n", filename);
        exit(EXIT_FAILURE);
    }

    printf("[*] fstat_filesize - file: %s, size: %lld\n", filename, statbuf.st_size);

    if (close(fd) == -1)
    {
        printf("failed to fclose %s\n", filename);
        exit(EXIT_FAILURE);
    }
}

void
stat_filesize(const char *filename)
{
    struct stat statbuf;

    if (stat(filename, &statbuf) == -1)
    {
        printf("failed to stat %s\n", filename);
        exit(EXIT_FAILURE);
    }

    printf("[*] stat_filesize - file: %s, size: %lld\n", filename, statbuf.st_size);

}

void
seek_filesize(const char *filename)
{
    int fd;
    off_t off;

    if (filename == NULL)
    {
        printf("invalid filename\n");
        exit(EXIT_FAILURE);
    }

    fd = open(filename, O_RDONLY, S_IRUSR | S_IRGRP);
    if (fd == -1)
    {
        printf("failed to open %s\n", filename);
        exit(EXIT_FAILURE);
    }

    off = lseek(fd, 0, SEEK_END);
    if (off == -1)
    {
        printf("failed to lseek %s\n", filename);
        exit(EXIT_FAILURE);
    }

    printf("[*] seek_filesize - file: %s, size: %lld\n", filename, (long long) off);

    if (close(fd) == -1)
    {
        printf("failed to close %s\n", filename);
        exit(EXIT_FAILURE);
    }
}

int
main(int argc, const char *argv[])
{
    int i;

    if (argc < 2)
    {
        printf("%s <file1> <file2>...\n", argv[0]);
        exit(0);
    }

    for(i = 1; i < argc; i++)
    {
        seek_filesize(argv[i]);
        stat_filesize(argv[i]);
        fstat_filesize(argv[i]);
        fseek_filesize(argv[i]);
    }

    return 0;
}

1
如果 off == (-1L),则不需要使用 (long) - Imobilis
ftell 返回一个 long,不幸的是。你需要使用 ftello 来返回一个 off_t。(或者显然在 Windows 上,_ftelli64(),因为它们似乎喜欢让编写可移植代码更加困难。)请参见另一个答案的讨论 - Peter Cordes
1
fstat 只有在你已经打开文件或作为打开文件过程的一部分时才有意义。你的 fstat_filesize 不是你想要以那种形式使用的,只有在你真的想保留 fd 并从中读取或做其他事情时才需要。用 open/fstat/close 没有任何优势,我会写一个函数来接受一个 FILE *fp(使用 fileno())或 int fd 。虽然你的函数只是打印结果而不是返回结果,但我想它们并不是要按原样使用的。 - Peter Cordes
1
另外,由于您没有将 O_CREAT 传递给 open,因此第三个参数未使用。在那里使用 S_IRUSR | S_IRGRP 是没有意义的。如果 open 创建文件,则会赋予它 0440r--r----- 权限(这将阻止其他任何人打开并写入该文件),但是如果没有 O_CREAT,则不会创建该文件,因此原型的 int open(const char *pathname, int flags); 形式适用。https://man7.org/linux/man-pages/man2/open.2.html - Peter Cordes
除了 fstat_filesize 的设计之外,这是一个有用的错误检查示例。但是你应该使用 fprintf(stderr, ...) 来输出错误信息。在使用 POSIX 的 stat 和相关函数的时候,你应该使用 strerror 来获取实际的失败原因,例如 ENOENT 的 "没有这样的文件或目录" 或 EPERM 的 "权限被拒绝"。这样更加有用,也是 Unix 程序报告错误的标准方式。(系统调用和文件名总比什么都没有好,如果你不告诉用户,他们可能不会考虑权限问题。) - Peter Cordes

9
你有没有考虑不计算文件大小,只在必要时扩展数组?这里有一个示例(省略了错误检查):
#define CHUNK 1024

/* Read the contents of a file into a buffer.  Return the size of the file 
 * and set buf to point to a buffer allocated with malloc that contains  
 * the file contents.
 */
int read_file(FILE *fp, char **buf) 
{
  int n, np;
  char *b, *b2;

  n = CHUNK;
  np = n;
  b = malloc(sizeof(char)*n);
  while ((r = fread(b, sizeof(char), CHUNK, fp)) > 0) {
    n += r;
    if (np - n < CHUNK) { 
      np *= 2;                      // buffer is too small, the next read could overflow!
      b2 = malloc(np*sizeof(char));
      memcpy(b2, b, n * sizeof(char));
      free(b);
      b = b2;
    }
  }
  *buf = b;
  return n;
}

这样做的好处是,即使在无法获取文件大小的流(如stdin)中也可以工作。


19
也许可以在这里使用 realloc 函数,而不是使用中间指针并且需要调用 free() - Victor Zamanian
这种方法的实际劣势是O(n^2)…要复制的东西的大小增加了。对于小文件来说还好,但对于大文件来说非常糟糕。如果您有1k块和100M文件,则最终需要复制(如果我算得没错的话)大约1E17字节。这可能是一个极端的例子,但它证明了为什么不应该这样做。 - Floris
3
除非我理解有误,否则每次都会将大小存储为双精度浮点数。因此,运行时间是O(n),而不是O(n^2)。这是通常用于std::vector及其类似物的相同分配策略。不管怎样,重新分配仍然比查询文件大小并一次性读取要低效。 - Joe
这个在每次重新分配时都会翻倍。任何大于1的常数因子调整大小都足以获得O(n)的界限,字面上的加倍可能过度,例如使用np += (np / 2) + (np / 4);来缩放1.75倍-所有整数,中间结果不会“早期”溢出。我更可能使用1.5,但1.75更能体现这个想法。当然要注意溢出,特别是任何前一个大小的倍数可能会在实际大小不同时溢出。如果您的文件大小为(2^31)-1,则这可能会尝试分配一个带有-(2^31)而不是2^31字节的缓冲区。 - user180247
我应该提醒一下,np += (np / 2) + (np / 4) 并不能精确地将数字乘以1.75 - 结果可能会太小,因为被截断的位没有进位传递 - 但对于这个目的来说应该足够了。如果要将数字乘以1.5,则 np += (np / 2); 应该是正确的。 - user180247

8
如果你使用Linux,强烈建议直接使用glib的g_file_get_contents函数。它处理加载文件、分配内存和处理错误的所有代码。

43
如果您使用的是Linux,并且想要依赖于glib,那么可以这样做。 - JesperE
2
问题还不算太糟糕,因为 glib 现在被 GTK 和 KDE 应用程序同时使用。它也可用于 Mac OS X 和 Windows,但在那里的标准化程度远远不及。 - Ben Combee
1
但是glib不是C++库吗?问题规定了C。 - Dave Appleton
5
不,glib非常是一个纯C的库,而不是C++。 - Nate C-K
@BenCombee 我上次检查时,Android 上没有 glib。 - Wyatt Ward
默认情况下,Android 没有安装 GTK 或 KDE。 - Shahe Ansar

-42
#include <stdio.h>

#define MAXNUMBER 1024

int main()
{
    int i;
    char a[MAXNUMBER];

    FILE *fp = popen("du -b  /bin/bash", "r");

    while((a[i++] = getc(fp))!= 9)
        ;

    a[i] ='\0';

    printf(" a is %s\n", a);

    pclose(fp);
    return 0;
}  

HTH


26
这个解决方案过于复杂且低效。根据上面的答案,没有必要执行命令并解析其输出。 - brandizzi
4
此解决方案仅适用于Linux系统。 - bobobobo

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