我是否认为此代码引入了未定义的行为?
#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
函数获取stream指向的流的文件位置指示符的当前值。对于二进制流, 该值是文件开头的字符数。 对于文本流,其文件位置指示符包含未指定 信息,可由fseek函数用于返回文件 流的位置指示器到达时的位置 ftell call;两个这样的返回值之间的差异不是 必然是一个有意义的措施,写入的字符数 或阅读。换句话说,当您处理以文本模式打开的文件时,
ftell()
返回的值无效......除了调用fseek()
之外。 特别是,它不一定告诉你有多少个字符 在流中直到当前点。因此,您无法使用
ftell()
的返回值来告诉您大小 文件,文件中的字符数或任何内容 (稍后调用fseek()
除外)。所以你无法获得文件大小 那样。好的,所以到了文本模式的地狱。有什么说我们只在二进制模式下工作? 正如C标准所说:“对于二进制流,值是数字 从文件开头的字符。“这听起来很有希望。
事实上确实如此。如果您在文件的末尾,并且您致电
ftell()
,您将找到文件中的字节数。好哇! 成功!我们现在需要做的就是到文件的末尾。并 这样做,你需要做的只是fseek()
与SEEK_END
,对吗?错误。
再次,从C标准:
将文件位置指示符设置为文件结尾,与
fseek(file, 0, SEEK_END)
一样,具有二进制流的未定义行为 (因为可能是尾随空字符)或任何流有 状态相关的编码,不能确定地在初始结束 转变状态。要理解为什么会这样:某些平台将文件存储为 固定大小的记录。如果文件短于记录大小,则 块的其余部分是填充的。当你寻求“结束”,为 效率的缘故它只会让你跳到最后 阻止...可能在数据实际结束后很久,一堆之后 填充。
所以,这是C中的情况:
- 在文字模式下,您无法获得
ftell()
的字符数。- 您可以在二进制模式下使用
ftell()
获取字符数...但您无法使用fseek(p_file, 0, SEEK_END)
搜索文件的末尾。
我没有足够的知识判断谁在这里,如果上述接受的答案确实与本文发生冲突,那么我就是在问这个问题。
答案 0 :(得分:4)
该文章的作者恶意忽略的是引用的上下文。
根据C11标准草案n1570,非规范脚注268 :
将文件位置指示器设置为文件结尾,如同 fseek(file,0,SEEK_END)具有二进制流的未定义行为 (因为可能是尾随空字符)或任何流有 状态相关的编码,不能确定地在初始结束 转变状态。
引用脚注的标准的规范部分是 7.21.3文件:
9尽管文本和二进制广泛的流都是概念上的 宽字符序列,与a关联的外部文件 面向广义的流是一系列多字节字符, 概括如下:
- 文件中的多字节编码可能包含 嵌入的空字节(与多字节编码不同,对内部使用有效) 该计划)。
- 文件不需要在初始移位状态下开始或结束。 268)
请注意,这涉及面向广播的流。
现在,在 7.21.9.2 fseek函数
中3对于二进制流,新位置以字符为单位测量 文件的开头是通过向文件添加偏移量获得的 由whence指定的位置。指定的位置是开始 文件的当前值是SEEK_SET,即文件的当前值 位置指示符,如果SEEK_CUR,或结束文件,如果SEEK_END。二进制 stream不需要有意义地支持具有whence值的fseek调用 SEEK_END。
语言是一个相当不那么可怕的最后一句:
“二进制流无需支持具有SEEK_END值的fseek调用。”