Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/c/61.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
scanf的缺点_C_Input_User Input_Scanf - Fatal编程技术网

scanf的缺点

scanf的缺点,c,input,user-input,scanf,C,Input,User Input,Scanf,我想知道scanf()的缺点 在许多网站上,我读到使用scanf可能会导致缓冲区溢出。这是什么原因?scanf是否还有其他缺点?scanf的问题(至少): 使用%s从用户处获取字符串,这可能导致字符串可能比缓冲区长,从而导致溢出 扫描失败导致文件指针处于不确定位置的可能性 我非常喜欢使用fgets来读取整行数据,这样可以限制读取的数据量。如果您有一个1K缓冲区,并使用fgets将一行读入其中,您可以通过没有终止换行符(尽管没有换行符的文件的最后一行)来判断该行是否太长 然后,您可以向用户投诉

我想知道
scanf()
的缺点


在许多网站上,我读到使用
scanf
可能会导致缓冲区溢出。这是什么原因?
scanf是否还有其他缺点?

scanf的问题(至少):

  • 使用
    %s
    从用户处获取字符串,这可能导致字符串可能比缓冲区长,从而导致溢出
  • 扫描失败导致文件指针处于不确定位置的可能性
我非常喜欢使用
fgets
来读取整行数据,这样可以限制读取的数据量。如果您有一个1K缓冲区,并使用
fgets
将一行读入其中,您可以通过没有终止换行符(尽管没有换行符的文件的最后一行)来判断该行是否太长

然后,您可以向用户投诉,或者为该行的其余部分分配更多空间(如有必要,可以连续分配,直到您有足够的空间为止)。在这两种情况下,都没有缓冲区溢出的风险

一旦你读入这一行,你就知道你在下一行,所以那里没有问题。然后,您就可以
sscanf
将您的字符串保存到您的核心内容,而无需保存和恢复文件指针以便重新读取

下面是一段代码,我经常使用它来确保在询问用户信息时不会出现缓冲区溢出

如果需要,它可以很容易地调整为使用标准输入以外的文件,您也可以让它在将缓冲区返回给调用方之前分配自己的缓冲区(并不断增加它,直到它足够大为止)(当然,调用方将负责释放它)

最后,进行一次测试运行以显示其实际效果:

$ printf "\0" | ./tstprg     # Singular NUL in input stream.
Enter string>
No input

$ ./tstprg < /dev/null       # EOF in input stream.
Enter string>
No input

$ ./tstprg                   # A one-character string.
Enter string> a
OK [a]

$ ./tstprg                   # Longer string but still able to fit.
Enter string> hello
OK [hello]

$ ./tstprg                   # Too long for buffer.
Enter string> hello there
Input too long [hello the]

$ ./tstprg                   # Test limit of buffer.
Enter string> 123456789
OK [123456789]

$ ./tstprg                   # Test just over limit.
Enter string> 1234567890
Input too long [123456789]
$printf“\0”|./tstprg#输入流中的单数。
输入字符串>
没有输入
$./tstprg
没有输入
$./tstprg#一个字符串。
输入string>a
好的[a]
$./tstprg#更长的字符串,但仍然能够适应。
输入string>hello
好的[你好]
$./tstprg#缓冲区太长。
在那里输入string>hello
输入太长[hello the]
$./tstprg#缓冲区的测试限制。
输入字符串>123456789
好的[123456789]
$./tstprg测试刚刚超出限制。
输入字符串>1234567890
输入太长[123456789]

是的,你说得对。在
scanf
系列(
scanf
sscanf
fscanf
等)中存在一个主要的安全漏洞,尤其是在读取字符串时,因为它们没有考虑缓冲区的长度(它们正在读取的缓冲区)

例如:

char buf[3];
sscanf("abcdef","%s",buf);

显然,缓冲区
buf
可以容纳MAX
3
char。但是
sscanf
会试图将
中的“abcdef”
放入其中,导致缓冲区溢出。

来自comp.lang.c常见问题解答:

scanf
有许多问题,请参见问题和。另外,它的
%s
格式与
get()
格式存在相同的问题(请参见问题)-很难保证接收缓冲区不会溢出

更一般地说,
scanf
是为相对结构化、格式化的输入而设计的(其名称实际上来源于“扫描格式化”)。如果你注意,它会告诉你它是成功了还是失败了,但它只能告诉你失败的大致位置,而不能告诉你失败的方式或原因。您几乎没有机会执行任何错误恢复

然而,交互式用户输入是结构化程度最低的输入。一个设计良好的用户界面将允许用户键入任何内容,不仅仅是字母或标点符号(当需要数字时),还可以键入比预期更多或更少的字符,或者根本没有字符(即,仅返回键),或者过早输入EOF,或者任何内容。使用
scanf
时,几乎不可能优雅地处理所有这些潜在问题;阅读整行文字(使用
fgets
或类似工具)然后使用
sscanf
或其他一些技术对其进行解释要容易得多。(像
strtol
strtok
atoi
这样的函数通常很有用;另请参见问题和。)如果确实使用了任何
scanf
变量,请确保检查返回值,以确保找到了预期数量的项目。此外,如果使用
%s
,请确保防止缓冲区溢出

顺便说一下,对
scanf
的批评不一定是对
fscanf
sscanf
的控诉
scanf
读取
stdin
,stdin通常是一个交互式键盘,因此受到的约束最小,导致的问题最多。另一方面,当数据文件具有已知格式时,可以使用
fscanf
读取它。使用
sscanf
解析字符串非常合适(只要检查了返回值),因为很容易重新获得控制权,重新启动扫描,如果输入不匹配则丢弃输入,等等

其他链接:

参考文献:K&R2第。下午7.4。159


很难让scanf做你想做的事情。当然,你可以,但是像是
scanf(“%s”,buf)
得到的一样危险(buf),正如大家所说

例如,paxdiablo在其函数to read中所做的工作可以通过以下方式完成:

scanf("%10[^\n]%*[^\n]", buf));
getchar();
上述操作将读取一行,将前10个非换行字符存储在
buf
中,然后丢弃所有字符,直到(包括)换行。因此,paxdiablo的函数可以使用
scanf
以下方式编写:

#include <stdio.h>

enum read_status {
    OK,
    NO_INPUT,
    TOO_LONG
};

static int get_line(const char *prompt, char *buf, size_t sz)
{
    char fmt[40];
    int i;
    int nscanned;

    printf("%s", prompt);
    fflush(stdout);

    sprintf(fmt, "%%%zu[^\n]%%*[^\n]%%n", sz-1);
    /* read at most sz-1 characters on, discarding the rest */
    i = scanf(fmt, buf, &nscanned);
    if (i > 0) {
        getchar();
        if (nscanned >= sz) {
            return TOO_LONG;
        } else {
            return OK;
        }
    } else {
        return NO_INPUT;
    }
}

int main(void)
{
    char buf[10+1];
    int rc;

    while ((rc = get_line("Enter string> ", buf, sizeof buf)) != NO_INPUT) {
        if (rc == TOO_LONG) {
            printf("Input too long: ");
        }
        printf("->%s<-\n", buf);
    }
    return 0;
}
如果发生溢流,则无法安全使用上述装置。即使在第一种情况下,读取字符串也是非常困难的
scanf("%10[^\n]%*[^\n]", buf));
getchar();
#include <stdio.h>

enum read_status {
    OK,
    NO_INPUT,
    TOO_LONG
};

static int get_line(const char *prompt, char *buf, size_t sz)
{
    char fmt[40];
    int i;
    int nscanned;

    printf("%s", prompt);
    fflush(stdout);

    sprintf(fmt, "%%%zu[^\n]%%*[^\n]%%n", sz-1);
    /* read at most sz-1 characters on, discarding the rest */
    i = scanf(fmt, buf, &nscanned);
    if (i > 0) {
        getchar();
        if (nscanned >= sz) {
            return TOO_LONG;
        } else {
            return OK;
        }
    } else {
        return NO_INPUT;
    }
}

int main(void)
{
    char buf[10+1];
    int rc;

    while ((rc = get_line("Enter string> ", buf, sizeof buf)) != NO_INPUT) {
        if (rc == TOO_LONG) {
            printf("Input too long: ");
        }
        printf("->%s<-\n", buf);
    }
    return 0;
}
int i;
scanf("%d", &i);
char *buf;
scanf("%ms", &buf); // with 'm', scanf expects a pointer to pointer to char.

// use buf

free(buf);
int i;
scanf("%10s", &i);
scanf("%10s", i);