如何在不使用 fseek 或 stat 的情况下在 C 中获取文件大小?



我正在为我的学校做一个项目,但我找不到如何获取文件的大小。由于我需要读取脚本并在程序中使用它,因此我需要文件大小才能使用读取或读取。

这是我为获取文件大小所做的工作,但它似乎不起作用。

int my_size(int filedesc)
{
int size = 1;
int read_output = 1;
char *buffer;
for (size = 1; read_output != 0 ; size++) {
buffer = malloc((size+1)*sizeof(char*));
read_output = read(filedesc, buffer, size);
free(buffer);
}
return(size);
}

而且我不允许使用 stat() 或 fseek() 作为这个项目的规则,也不能使用任意大小(如 100)的 read 或 fread,因为给出的脚本可以小或大。

如果你可以依赖输入是一个持久文件(即驻留在存储介质上),并且该文件在程序运行期间没有被修改,那么你可以预先读取它到最后以计算其中的字节数,然后倒带。

但在学术练习之外,禁止通过stat()fseek()和类似方式测量大小的通常原因是输入可能不驻留在存储介质上,因此

  1. 如果不阅读它,您就无法确定其大小,而且
  2. 你无法倒带它或在其中寻求。

那么诀窍不是如何提前确定尺寸,而是如何在不提前测量尺寸的情况下做到这一点。 至少有两种主要策略:

  • 首先不要依赖将所有内容一次性存储在内存中。 相反,在读取其内容时对其内容进行操作,在任何给定时间仅保留足够的内存。

  • 或者,动态适应文件大小。 对此有很多变化。例如,如果您只是将文件读入整体块,那么您可以malloc()空间并在发现需要更多时realloc()。 或者,您可以将内容存储在链表中,根据需要分配新的列表节点。

至于问题中提出的方法,有几个问题。 这似乎是尝试按照我第一次描述的那样 - 将文件读取到最后以确定其大小 - 但是

  1. 它似乎假设每个read()都将从文件的开头开始,或者如果read()无法读取整个文件,则可能会失败。 事实并非如此。 每个read()都将从文件的当前位置开始,并将文件保留在传输的最后一个字节之后。

  2. 因为它改变了文件的位置,所以您的方法将要求文件在之后倒带 - 例如通过lseek()。 但是,如果lseek()可以用于此目的(并注意我之前关于您无法在其中查找的文件的评论),那么它将提供一种更干净的方法来测量文件大小。

  3. 您不考虑 I/O 错误。 如果发生了一个,那么它可能会将你的程序发送到一个无限循环。

  4. 动态分配相对昂贵,而且您正在做很多事情。 如果你想实现预读策略,那么这将是一个更好的实现:

    ssize_t count_bytes(int fd) {
    ssize_t num_bytes = 0;
    char buffer[2048];
    ssize_t result;
    do {
    result = read(fd, buffer, sizeof(buffer));
    if (result < 0) {
    // handle error ...
    }
    num_bytes += result;
    while (result > 0);
    return num_bytes;
    }
    

在可执行文件上使用gdb调试器或 strace(1),使用所有警告和调试信息进行编译:gcc -Wall -Wextra -g使用 GCC。仔细阅读 read(2) 的文档,以及你正在使用的每个函数的文档(包括 malloc(3),你忘记测试它的失败)。

你需要使用 read(2) 的结果(实际读取字节数)。并且您需要专门处理错误情况(当read给出 -1 时)。

对于足够长的文件,可能发生的情况是,在第一个循环中,您正在读取 1 个字节,在第二个循环中您正在读取 2 个字节,在第三个循环中您读取了 3 个字节,依此类推......(在这种情况下,您忘记计算 1+2+3)。

你应该对所有read_output进行累积和求和,并且当read(2)给出的小于size时,你应该处理这种情况(这应该发生在你的read最后一次给出非零时)。

相反,我建议使用固定缓冲区(常量或固定大小),并重复执行 read(2),但小心使用返回的字节计数(此外,处理错误和 EOF 条件)。

请注意,系统调用(在系统调用(2)中列出)非常昂贵。根据经验,您应该读取(2)或写入(2)几千字节的缓冲区(并仔细处理返回的字节计数,并针对错误对其进行测试,请参阅errno(3))。每次一次只read几个字节的程序效率低下。

此外,malloc(或realloc)相当昂贵。将堆分配的大小增加 1 是丑陋的(因为您在每个循环中都调用malloc;在您的情况下,您甚至不需要使用malloc)。你最好使用一些几何级数,也许是newsize = 4*oldsize/3 + 10;(或类似)。

最新更新