从文件读取的意外输出

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", &amp;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 &gt; 0 避免了长度为0 的字符串的未定义行为(如果行的第一个字符是'\0',则会发生这种情况)。

【讨论】:

黑客攻击:aux.name[strlen(aux.name) - 1]aux.name[len - 1] 如果第一个字符 readnull 字符,则会遭受 UB。如果你想要len,请使用if(len &gt; 0 &amp;&amp; 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

【讨论】:

以上是关于从文件读取的意外输出的主要内容,如果未能解决你的问题,请参考以下文章

Flutter 意外退出,读取依赖时出错

Javascript - 无法读取 json 键值 - JSON 中位置 1 的意外令牌 i - sql 查询输出问题

如下:为啥C语言读取文件中的数据并输出时有乱码出现?本来输入的是数字,然后从文件中读取后就变汉字

上传文件停止并在套接字异常上读取意外的 EOF

python从任意文件读取邮件地址输出的代码

Java简单从文件读取和输出