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

2izufjch  于 2023-02-03  发布在  其他
关注(0)|答案(2)|浏览(112)

我在这里读到feof或更准确地说,使用!feof在文件中搜索信息是一个坏习惯。
我所理解的是,它是坏的,因为它在调用函数或进程或类似的东西之前从FILE指针读取信息。
有一个do/while循环,内部有fscanf!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);
}
xuo3flqw

xuo3flqw1#

函数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));

如果fscanf由于遇到文件末尾而失败并返回EOF,则它将不向e.fname写入任何内容。
如果这种情况发生在循环的第一次迭代中,那么e.fname的内容将是不确定的,并且随后的函数调用strcmp(e.fname,str)将调用未定义的行为(即,您的程序可能崩溃),除非e.fname碰巧包含终止空字符。
如果这不是在第一次迭代中发生的,而是在循环的后续迭代中发生的,那么e.fname的内容将包含前一次循环迭代的内容,因此您实际上将处理fscanf的最后一次成功调用两次。
在这个特定的例子中,两次处理fscanf的最后一次成功调用是无害的,只是会稍微浪费CPU和内存资源,但是在大多数其他的例子中,两次处理最后一次输入会导致程序不能正常工作。
有关详细信息,请参见以下问题:
Why is “while( !feof(file) )” always wrong?
如果将循环更改为

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;
}

从而在循环的中间检查循环条件,则上述问题将消失。
但是,一般来说,检查fscanf的返回值比调用feof更好,例如:

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;
}

另外,您不需要标记变量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);
}

另一个问题是,当将%sscanffscanf一起使用时,通常还应添加宽度限制,以防止可能出现buffer overflow。例如,如果e.fname的大小为100字符,则应使用%99s来限制写入99的字节数加上终止空字符。

ztigrdn8

ztigrdn82#

调用feof会询问“在这个流上的前一个操作中遇到了文件结束还是错误?”
如果你使用feof来回答这个问题,那就好了。但是,你使用feof来期望你的next操作从文件中读取数据,那就错了。前一个操作可能在文件结束之前**就结束了,所以feof说“no”,但是文件中没有留下任何要读取的数据。
标准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.
}

请注意,调用fscanf然后调用feof将告诉您fscanf是否遇到了文件尾或输入错误,但它不会告诉您fscanf是否读取了一些输入并分配了一些值,但遇到了文件尾并且没有完成。如果您只阅读一个内容,则可以使用fscanf后跟feof,但是更复杂的程序可能需要区分部分输入。

相关问题