从文件读取的意外输出
Posted
技术标签:
【中文标题】从文件读取的意外输出【英文标题】:Unexpected output reading from a file 【发布时间】:2020-05-09 16:06:07 【问题描述】:我有一个想要阅读的文本文件。该文件有以下内容:
Asdsf adsfsd
54
asdfa adwfasd
12
asdf adf
545
asdf asdfasfd
3243
adfasf asdfasdf
324324
asfda asdfasdf
3124
adfa asdfas
432
asdf ad
和我的代码:
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
struct Element
int edad;
char name[50];
;
int main()
struct Element aux;
FILE* fitxer;
fopen_s(&fitxer, "Text.txt", "r");
if (fitxer != NULL)
while (!feof(fitxer))
fgets(aux.name, 50, fitxer);
aux.name[strlen(aux.name) - 1] = '\0';
int ret = fscanf_s(fitxer, "%i", &aux.edad);
char endl;
fscanf_s(fitxer, "%c", &endl);
printf("%d %s \n", aux.edad, aux.name);
fclose(fitxer);
else
printf("Error: File not found.");
我之前遇到过问题,因为我不知道f_scanf
不带结束符。现在的问题是文件中有一些字符串被截断了。输出:
54 Asdsf adsfsd
12 asdfa adwfasd
545 asdf adf
3243 asdf asdfasfd
324324 adfasf asdfasdf
3124 asfda asdfasdf
432 adfa asdfas
432 asdf a
例如,在这个例子中,最后一个字母被切掉了。我怀疑它与转换为字符串有关,添加了'\0'
字符,但我找不到错误。
另外我想问一下有没有办法让它更优雅。
【问题讨论】:
***.com/q/5431941/905902 【参考方案1】:至少 3 个问题:
错误的文件结尾测试,避免幻数
ref
//while (!feof(fitxer))
// fgets(aux.name, 50, fitxer);
while (fgets(aux.name, sizeof aux.name, fitxer))
fscanf_s(fitxer, "%c", &endl);
缺少增强功能。
如果有兴趣,请研究fscanf_s()
,或者更好的是,只需使用fgets()
进行输入。
错误的代码会终止潜在的试用 '\n'
替代方案:12
// aux.name[strlen(aux.name) - 1] = '\0';
aux.name[strcspn(aux.name, "\n")] = '\0';
【讨论】:
使用 fgets 会给我完整的行,我怎么能把它转换成整数和字符串? @Norhther "fgets 将给我完整的行" 作为字符串。所以“我怎么能把它转换成(a)......字符串?”已经完成了。将字符串转换为整数研究strtol()
,sscanf(s, "%d"...
。
@Reinstate Monica 我明白了。由于可能的溢出,我使用 sscanf (我正在使用 VS)收到编译器警告,但我已经使用 gets() 限制缓冲区大小,所以我认为它是安全的。非常感谢。
@Norhther 很可能警告是著名的#C4996。忽略它或this。它鼓励针对一般问题的 MS 特定解决方案。更好的是,与其使用 VS C 编译器,它遵循 30 年(和 3 个 C 版本) C89 ,而是使用遵循 C99、C11 或 C18 的现代编译器。我使用 gcc(在 Windows 中)。【参考方案2】:
与
aux.name[strlen(aux.name) - 1] = '\0';
你摆脱了fgets
的一个众所周知的行为:它将整行存储到输出缓冲区包含 '\n'
字符。
但是如果那个字符不存在怎么办?你会砍掉最后一个字符。
这正是您阅读文件的最后行时发生的情况。由于没有尾随 '\n'
字符,因此一旦到达文件末尾,fgets
就会停止。
要修复它,只需检查要替换的字符是否是预期的。
类似这样的:
size_t len = strlen(aux.name);
if(len > 0 && aux.name[len - 1] == '\n')
aux.name[len - 1] = '\0';
检查len > 0
避免了长度为0 的字符串的未定义行为(如果行的第一个字符是'\0'
,则会发生这种情况)。
【讨论】:
黑客攻击:aux.name[strlen(aux.name) - 1]
和 aux.name[len - 1]
如果第一个字符 read 是 null 字符,则会遭受 UB。如果你想要len
,请使用if(len > 0 && aux.name[len - 1] == '\n') aux.name[--len] = '\0';
@chux-ReinstateMonica 我将根据您的改进编辑答案。无论如何,我不得不说问题的第一句话是“我有一个要阅读的文本文件”。包含空字符的文件不是文本文件。;)
注意:UTF16 文本文件通常包含空字节。很容易让用户在这里无意中错误地读取这样的 文本文件 并导致代码出现某事 错误。因此,健壮的代码不会强制用户提供经过审查的输入,而是处理good, bad and ugly。
@chux-ReinstateMonica touchet on UTF16。我没有考虑他们。我编辑了它。 Ps:我不明白健壮的代码和你链接的电影之间的关系。很遗憾,因为我是 Leone 的超级粉丝。
只是用户输入(文本文件)可能很好(包含通常的预期数据,布局很好,没有 \0,\r,最后一行以 \n 结尾,没有行太长, ASCII 之外没有字符,...) 或丑陋的(无辜地弄乱了这些先前的要求之一)或糟糕的(专门设计用于攻击代码)。电影名称集中体现了健壮代码需要处理的这 3 种情况。【参考方案3】:
aux.name[strlen(aux.name) - 1] = '\0';
这一行删除了您使用fgets
读取的字符串中的最后一个字符。对于大多数行,该字符是行尾的\n
。但我假设你的最后一行最后没有换行符。所以你砍掉最后一个字符。
要解决此问题,您应该只删除等于'\n'
的最后一个字符。
PS:您对fscanf_s
的最后一次调用失败,您最终打印出与上一行相同的号码。我不确定这是不是故意的。
PPS:如果您对fscanf_s
的最后一次调用没有失败,那么您的while 循环将循环一次,因为feof
仅在先前的读取由于文件结尾而失败时才返回true。因此,您可能希望直接检查您的读取操作是否失败,而不是使用feof
。
【讨论】:
以上是关于从文件读取的意外输出的主要内容,如果未能解决你的问题,请参考以下文章
Javascript - 无法读取 json 键值 - JSON 中位置 1 的意外令牌 i - sql 查询输出问题