多个fscanf

时间:2015-05-14 17:01:16

标签: c arrays pointers scanf buffer-overflow

我编写了以下程序,用于将文件中的字符串读入变量“title”:

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int m, b;
    char *title;
    FILE *fp;

    fp = fopen("input2.txt", "r");
    if (fp == NULL)
    {
         printf ("Error: file cannot be found\n");
         return 1;
    }

    fscanf(fp, "<%d>\n<%d>", &m, &b);
    printf("%d\n%d", m, b);
    fscanf(fp, "<%s>", title);

    fclose(fp);
    return 0;
}

上述程序在第二次调用fscanf时崩溃。为什么会这样?

2 个答案:

答案 0 :(得分:5)

您的主要问题是您没有为要读取的字符串分配空间。您可以通过多种方式执行此操作:

char title[256];

或:

char *title = malloc(256);
if (title == NULL)
{
    fprintf(stderr, "Out of memory\n");
    exit(1);
}

其中任何一个都应该用于:

if (fscanf(fp, " <%255[^>]>", title) != 1)
{
    fprintf(stderr, "Oops: format error\n");
    exit(1);
}

或者,如果您的系统的实施符合fscanf()符合POSIX 2008,则可以使用m修饰符%s(或%c 1}},或者,在这种情况下,扫描集%[...] - 更多内容如下:

char *title = 0;

if (fscanf(fp, " <%m[^>]>", &title) != 1)  // Note the crucial &
{
    fprintf(stderr, "Oops: format error\n");
    exit(1);
}

这样,如果fscanf()完全成功,该函数将为标题分配内存。如果失败,则内存将被释放(或从未分配)。

请注意,我将%s更改为%m[^>]。这是必要的,因为原始转化永远不会与>匹配。如果输入中有>,它将被合并到结果字符串中,因为它会读取空格,>不是空格。此外,您无法判断尾随上下文是否匹配 - 原始格式中的>,并且它仍然是一个问题(或不是)修改后的代码我建议。

我还在字符串的开头添加了一个空格以匹配可选的空格。如果不这样,字符串开头的<必须与第二个数字后的>在同一行,假设>完全存在。您还应该检查第一个fscanf()的回复:

if (fscanf(fp, "<%d>\n<%d>", &m, &b) != 2)
{
    fprintf(stderr, "Oops: format error\n");
    exit(1);
}

请注意,嵌入式换行只是查找><之间的空白区域 - 即零个或多个空格,制表符或换行符。另请注意,您永远不会知道第二个>是否匹配。

您可以使用exit(EXIT_FAILURE);代替exit(1); - 或者,由于此代码位于main(),您可以使用return 1;return(EXIT_FAILURE);在任何一种情况下,括号都是可选的,但它们的存在在某些人中引起了无根据的愤怒。

您还可以改进错误消息。您应该考虑使用fgets()或POSIX&#39; getline()后跟sscanf()因为它可以更容易(到目前为止)做出良好的错误报告,而且您可以轻松地重新扫描数据如果第一次尝试转换它失败了。

答案 1 :(得分:1)

此:

    char *title;

只是一个指向char的指针。如果fscanf为其写入多个字符,则会破坏

之后发生在内存中的任何内容

您需要做以下两件事之一:

    char title[50]; // Holds up to 49 characters, plus termination

或者:

    #include <stdlib.h>
    // ...
    char *title = malloc(50 * sizeof(char)); // Same capacity as above
    if (title == NULL) {
        // handle out of mem error
    }
    // ...
    free (title);

第一个选项显然要简单得多,但要求你在编译时知道你的数组大小。

如果您不熟悉编程,并且尚未遇到指针和动态内存分配,请暂时坚持使用第一个选项。