【问题标题】:Multiple fscanf多个 fscanf
【发布时间】:2015-05-14 17:01:15
【问题描述】:

我编写了以下程序,旨在将文件中的字符串读取到变量“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 时崩溃。为什么会这样?

【问题讨论】:

    标签: c arrays pointers scanf buffer-overflow


    【解决方案1】:

    您的主要问题是您没有为要读取的字符串分配空间。您可以通过多种方式做到这一点:

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

    或者,如果您的系统具有符合 POSIX 2008 的 fscanf() 实现,则可以将 m 修饰符用于 %s(或使用 %c,或者在本例中为扫描集%[...] — 更多内容见下文):

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

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

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

    我还在字符串的开头添加了一个空格来匹配可选的空格。否则,字符串开头的&lt; 必须与第二个数字之后的&gt; 在同一行,假设&gt; 完全存在。您还应该检查第一个fscanf()的返回:

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

    请注意,嵌入的换行符只是在&gt;&lt; 之间查找空格——即零个或多个空格、制表符或换行符。另请注意,您永远不会知道第二个 &gt; 是否匹配。

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

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

    【讨论】:

    • m 为指针分配内存?从来不知道。之后我们需要free吗?
    • 正确 — 如果您有支持 POSIX-2008 功能的 scanf() 版本,那么 %ms 或类似的转换会为指针分配内存,就像使用 malloc() 一样。而且,转换不需要char *,而是需要char **。是的,您需要在之后释放分配的内存。 scanf() 函数将内存的所有权移交给调用代码,调用代码负责释放它。
    • 好的。谢谢你提供的详情。它在我的系统中不起作用(GCC 提供 warning: unknown conversion type character 'm' in format),尽管它在 rextester 中起作用。
    • 您使用的是哪个平台?它适用于 Linux。它在 BSD 或 Mac OS X 上不起作用(但 n$ 符号也不起作用)。它很有用,但尽管是 POSIX 标准功能,但可移植性有限。
    • 我在 Windows 上。 n$ 符号是什么?
    【解决方案2】:

    这个:

        char *title;
    

    只是一个指向字符的指针。如果 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);
    

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

    如果您是编程新手,并且还没有遇到过指针和动态内存分配,请暂时坚持第一个选项。

    【讨论】:

    • 请考虑char *title = malloc(50)char *title = malloc(50 * sizeof *title),而不是char *title = malloc(50 * sizeof(char))sizeof(char)总是 1.
    • 您不能将一个字符写入随机指针。如果它指向未分配的内存,它将在第一次取消引用时崩溃。我认为你的意思是如果 title != NULL 释放它,而不是当它为 NULL 时
    • 您不必在调用free() 之前测试NULL
    • @NisseEngström:实际上,情况比这更糟。该测试确保free() 是空操作,并在free() 必要且有用时泄漏内存。可能是 if (title != NULL) free(title); 是有意的,但你说得对,写 free(title); 更简单。
    • 糟糕。固定的。感谢收看。
    猜你喜欢
    • 1970-01-01
    • 2019-03-05
    • 2016-10-12
    • 2016-03-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多