【发布时间】:2017-09-22 04:38:55
【问题描述】:
这段代码引入了未定义的行为,我说得对吗?
#include <stdio.h>
#include <stdlib.h>
FILE *f = fopen("textfile.txt", "rb");
fseek(f, 0, SEEK_END);
long fsize = ftell(f);
fseek(f, 0, SEEK_SET); //same as rewind(f);
char *string = malloc(fsize + 1);
fread(string, fsize, 1, f);
fclose(f);
string[fsize] = 0;
我问的原因是,此代码被发布为以下问题的公认答案:C Programming: How to read the whole file contents into a buffer
但是,根据以下文章:How to read an entire file into memory in C++(尽管它的标题,它也涉及 C,所以坚持我):
假设您正在编写 C,并且您有一个
FILE*(您知道点 到文件流,或者至少是一个可搜索的流),并且您想要 确定在缓冲区中分配多少个字符来存储 流的全部内容。你的第一直觉可能是 写这样的代码:// Bad code; undefined behaviour fseek(p_file, 0, SEEK_END); long file_size = ftell(p_file);似乎合法。但随后你开始变得奇怪。有时 报告的大小大于磁盘上的实际文件大小。有时 它与实际文件大小相同,但字符数 你读的不一样。到底是怎么回事?
有两个答案,因为这取决于文件是否已经 以文本模式或二进制模式打开。
以防万一您不知道区别:在默认模式下 - 文本 模式——在某些平台上,某些字符会被翻译 阅读过程中的各种方式。最著名的是在Windows上, 写入文件时,换行符被转换为
\r\n,并且 读时反译。换句话说,如果文件 包含Hello\r\nWorld,会读作Hello\nWorld;文件 大小为 12 个字符,字符串大小为 11。鲜为人知的是0x1A(或Ctrl-Z)被解释为文件的结尾,所以如果文件 包含Hello\x1AWorld,它将被读取为Hello。此外,如果 内存中的字符串是Hello\x1AWorld,你把它写到一个文件中 文本模式,文件将为Hello。在二进制模式下,没有 翻译完成——文件中的任何内容都会读入您的 程序,反之亦然。你马上就可以猜到文本模式会让人头疼—— 至少在 Windows 上。更一般地说,根据 C 标准:
ftell函数获取流指向的流的文件位置指示符的当前值。对于二进制流, 该值是从文件开头开始的字符数。 对于文本流,其文件位置指示符包含未指定 信息,可由 fseek 函数用于返回文件 流的位置指示器到它在时间的位置 ftell 通话;两个这样的返回值之间的差异不是 一定是对所写字符数的有意义的度量 或阅读。换句话说,当您处理以文本模式打开的文件时,
ftell()返回的值是无用的……除了对fseek()的调用。 特别是,它不一定告诉你有多少个字符 在流中直到当前点。所以你不能用
ftell()的返回值来告诉你 文件,文件中的字符数,或任何东西 (除了以后打电话给fseek())。所以你无法获得文件大小 那样。好的,文本模式下地狱。什么说我们只在二进制模式下工作? 正如 C 标准所说:“对于二进制流,值是数字 文件开头的字符数。”这听起来很有希望。
确实如此。如果您在文件的末尾,并且您调用
ftell(),你会发现文件中的字节数。嘘! 成功!我们现在需要做的就是到达文件的末尾。并 这样做,你需要做的就是fseek()和SEEK_END,对吧?错了。
再一次,来自 C 标准:
将文件位置指示器设置为文件结尾,与
fseek(file, 0, SEEK_END)一样,对于二进制流具有未定义的行为 (因为可能的尾随空字符)或任何流 状态相关的编码,并不一定会在初始状态结束 转换状态。要了解为什么会这样:一些平台将文件存储为 固定大小的记录。如果文件短于记录大小,则 块的其余部分被填充。当你寻求“终点”时, 为了效率,它只会让你跳到最后 块......可能在数据实际结束之后很久,在一堆之后 填充。
所以,这是 C 中的情况:
- 在文本模式下,您无法获取
ftell()的字符数。ftell()可以在二进制模式下获取字符数……但您不能使用fseek(p_file, 0, SEEK_END)查找文件末尾。
我没有足够的知识来判断谁在这里,如果前面接受的答案确实与本文冲突,所以我问这个问题。
【问题讨论】:
-
一件事,你没有检查
malloc()的返回值,如果失败,你就有了UB。 -
@SouravGhosh 当然可以,但这不是这里的核心问题。
-
正确,这就是为什么它是评论,而不是答案。 :)
-
见this answer。这是未定义的行为。所以它不是便携式的。
-
最健壮和便携的方法仍然是读取字符直到 EOF 并计算它们。 (当您使用它时,您可以将它们存储到一个数组中并在需要时调整数组的大小)
标签: c stream undefined-behavior file-read