使用 !feof 搜索文件的安全性如何?

How safe is using !feof in searching a file?

提问人:ayoub hmani 提问时间:1/29/2023 最后编辑:ayoub hmani 更新时间:2/1/2023 访问量:150

问:

我在这里读到,或者更准确地说,在文件中搜索信息时使用是一个坏习惯。feof!feof

我的理解是,这很糟糕,因为它在调用函数或进程或类似的东西之前从指针读取信息。FILE

在内部有一个 / 循环作为退出条件不是很好吗?dowhilefscanf!feof

这是我做的一个搜索功能:

typedef struct
{
    char lname[20] , fname[20];
    int nchildren;
}employee;
void searchemployee(char *filename , char *str)
{
    employee e;
    FILE *f;
    int c;
    f = fopen(filename, "r");
    if (f == NULL)
        printf("file couldn't be loaded\n");
    else {
        c = 0;
        do {
            fscanf(f, "%s %s %d\n", e.fname, e.lname, &e.nchildren);
            if (strcmp(e.fname, str) == 0)
                c = 1;
        } while (c == 0 && !feof(f));
        if (c != 1)
            printf("employee not found\n");
        else
            printf("employee : %s %s| children : %d\n", e.fname, e.lname, e.nchildren);
    }
    fclose(f);
}
C eof Feof 特性

评论

0赞 KamilCuk 1/29/2023
fscanf(f,"%s %s- 如果第一个有效,但第二个失败怎么办?%s%s
0赞 Weather Vane 1/29/2023
使用 控制循环。无论如何,您都应该检查它是否转换了所需的项目。旁白:从格式字符串中删除换行符。请参阅 scanf() 格式字符串中尾随空格的影响是什么?fscanffscanf
0赞 Steve Summit 1/29/2023
只是不要以为是问,“我是否已经到了文件末尾?确定是否完成读取的方法是读取函数(、等)返回错误代码。其中一个函数返回错误代码后,如果您想知道“错误”是由于命中文件末尾,还是出于其他原因,则可以使用 or 来确定。feoffgetsfscanffreadfeofferror
0赞 chqrlie 1/29/2023
3 个简单的规则:1.永远不要使用,2.不要使用循环,3.总是测试返回值。4.避免使用.feof()do / whilefscanf()fscanf()

答:

3赞 Andreas Wenzel 1/29/2023 #1

函数的返回值指定上一个输入操作是否遇到文件末尾。此函数不指定下一个输入是否会遇到文件末尾。feof

问题

do{
    fscanf(f,"%s %s %d\n",e.fname,e.lname,&e.nchildren);
    if (strcmp(e.fname,str)==0)
        c=1;
}while(c==0 && !feof(f));

是如果由于遇到文件末尾而失败并返回,那么它将不写入任何内容。fscanfEOFe.fname

如果这种情况发生在循环的第一次迭代中,则 的内容将是不确定的,后续的函数调用将调用未定义的行为(即您的程序可能会崩溃),除非碰巧包含终止 null 字符。e.fnamestrcmp(e.fname,str)e.fname

如果这不是在第一次迭代中发生,而是在循环的后续迭代中发生,则 的内容将包含上一次循环迭代的内容,因此您将有效地处理两次的最后一次成功调用。e.fnamefscanf

在这种特定情况下,处理两次的最后一次成功调用是无害的,除了会稍微浪费 CPU 和内存资源。但是,在大多数其他情况下,将最后一个输入处理两次将导致程序无法按预期工作。fscanf

有关详细信息,请参阅以下问题:

为什么 “while( !feof(file) )” 总是错的?

如果将循环更改为

for (;;) {
    fscanf(f,"%s %s %d\n",e.fname,e.lname,&e.nchildren);
    if ( c != 0 || feof(f) )
        break;
    if (strcmp(e.fname,str)==0)
        c=1;
}

这样在循环中间检查循环条件,那么上面提到的问题就会消失。

但是,通常最好检查 的返回值而不是调用 ,例如:fscanffeof

c = 0;

while ( c == 0 && fscanf(f,"%s %s %d\n",e.fname,e.lname,&e.nchildren) == 3 ) {
    if (strcmp(e.fname,str)==0)
        c=1;
}

此外,您不需要 flag 变量 。我建议你合并这些线c

if (c!=1)
    printf("emplyee not found\n");
else
    printf("employee : %s %s| children : %d\n",e.fname,e.lname,e.nchildren);

部分进入循环,如下所示:

void searchemployee( char *filename, char *str )
{
    employee e;
    FILE *f = NULL;

    //attempt to open file
    f = fopen( filename, "r" );
    if ( f == NULL )
    {
        printf( "file couldn't be loaded\n" );
        goto cleanup;
    }

    //process one employee record per loop iteration
    while ( fscanf( f, "%s %s %d\n", e.fname, e.lname, &e.nchildren ) == 3 )
    {
        //check whether we found the target record
        if ( strcmp(e.fname,str) == 0 )
        {
            printf(
                "employee : %s %s| children : %d\n",
                e.fname, e.lname, e.nchildren
            );
            goto cleanup;
        }
    }

    printf( "employee not found.\n");

cleanup:
    if ( f != NULL )
        fclose(f);
}

另一个问题是,当使用 with 或 时,通常还应该添加宽度限制,以防止可能的缓冲区溢出。例如,如果字符大小为字符,则应使用限制写入的字节数加上终止 null 字符。%sscanffscanfe.fname100%99s99

评论

0赞 chqrlie 1/29/2023
fscanf(f, "%s"...)无法避免输入时间长于预期时的未定义行为。
0赞 Andreas Wenzel 1/29/2023
@chqrlie:由于 OP 没有向我们展示 的定义,我们无法知道 和 的大小,因此我无法添加宽度限制。employeee.fnamee.lname
1赞 Andreas Wenzel 1/29/2023
@chqrlie:我现在在我的回答中添加了一段提到这个问题的段落。
1赞 Steve Summit 1/29/2023
@chqrlie 这里有一个异端的想法:在宏伟的计划中,在没有大小限制的情况下使用实际上是可以的。你应该只在你 C 编程生涯的最初几周内使用 和 朋友。在最初的几周里,可以说,在你开始考虑缓冲区溢出之前,你还有其他东西需要学习。"%s"scanf
3赞 chqrlie 1/29/2023
@SteveSummit:我不反对......首先使用和朋友,然后了解为什么他们不是你的朋友:)scanf
1赞 Eric Postpischil 1/29/2023 #2

调用会询问以下问题:“在此流的上一个操作中是否遇到文件结束或错误?feof

如果你习惯于回答这个问题,那很好。但是,您过去常常期望您的下一个操作将从文件中读取数据,这是错误的。上一个操作可能在文件结束之前结束,因此说“否”,但文件中没有剩余任何内容可供读取。feoffeoffeof

标准 C 库中的文件/流函数旨在告诉您它们何时因到达文件末尾而失败。应使用每个函数提供的返回值(或其他指示)来测试问题:

if (3 != fscanf(f, "%s %s %d\n", e.fname, e.lname, &e.nchildren))
{
    // Handle fact that fscanf did not read and convert 3 values.
}

int x = getchar();
if (x == EOF)
{
    // Handle fact that fscanf did not read and convert 3 values.
}

请注意,调用 And Then 将判断是否遇到文件结束或输入错误,但它不会告诉您是否读取了一些输入并分配了一些值,但随后遇到了文件结束并且没有完成。如果您只读取一件事,您可能会侥幸逃脱,然后是 ,但更复杂的程序可能需要区分部分输入。fscanffeoffscanffscanffscanffeof