当我尝试用GCC编译使用gets()函数的C代码时,我得到这个警告:

(.text+0x34):警告:' gets'函数是危险的,不应该使用。

我记得这与堆栈保护和安全性有关,但我不确定确切的原因。

我如何删除这个警告,为什么使用gets()会有这样的警告?

如果gets()是如此危险,那么为什么我们不能删除它?


当前回答

为什么gets()是危险的

第一个互联网蠕虫(Morris internet worm)大约在30年前(1988-11-02)逃脱,它使用gets()和缓冲区溢出作为从一个系统传播到另一个系统的方法之一。基本的问题是,函数不知道缓冲区有多大,所以它继续读取,直到找到换行符或遇到EOF,并可能溢出给定的缓冲区边界。

你应该忘记你曾经听说过gets()的存在。

C11标准ISO/IEC 9899:2011取消了gets()作为标准函数,这是a Good Thing™(它在ISO/IEC 9899:1999/Cor中被正式标记为“过时”和“已弃用”。3:2007 - C99的技术勘误3,然后在C11中删除)。遗憾的是,由于向后兼容的原因,它将在库中保留很多年(意思是“几十年”)。如果由我来决定,gets()的实现将变成:

char *gets(char *buffer)
{
    assert(buffer != 0);
    abort();
    return 0;
}

考虑到您的代码迟早会崩溃,最好是尽早解决问题。我会准备添加一个错误消息:

fputs("obsolete and dangerous function gets() called\n", stderr);

如果链接gets(),现代版本的Linux编译系统会生成警告——对于其他一些也存在安全问题的函数(mktemp(),…)也是如此。

gets()的替代方案

fgets ()

正如其他人所说,gets()的规范替代方法是指定stdin作为文件流的fgets()。

char buffer[BUFSIZ];

while (fgets(buffer, sizeof(buffer), stdin) != 0)
{
    ...process line of data...
}

没有人提到的是gets()不包括换行符,但fgets()包含。因此,你可能需要使用fgets()的包装器来删除换行符:

char *fgets_wrapper(char *buffer, size_t buflen, FILE *fp)
{
    if (fgets(buffer, buflen, fp) != 0)
    {
        size_t len = strlen(buffer);
        if (len > 0 && buffer[len-1] == '\n')
            buffer[len-1] = '\0';
        return buffer;
    }
    return 0;
}

或者,更好:

char *fgets_wrapper(char *buffer, size_t buflen, FILE *fp)
{
    if (fgets(buffer, buflen, fp) != 0)
    {
        buffer[strcspn(buffer, "\n")] = '\0';
        return buffer;
    }
    return 0;
}

此外,正如caf在评论中指出的,paxdiablo在回答中显示的那样,使用fgets()可能会在一行中留下数据。我的包装器代码将这些数据留待下次读取;如果你喜欢,你可以很容易地修改它以吞噬其余的数据行:

        if (len > 0 && buffer[len-1] == '\n')
            buffer[len-1] = '\0';
        else
        {
             int ch;
             while ((ch = getc(fp)) != EOF && ch != '\n')
                 ;
        }

剩余的问题是如何报告三种不同的结果状态——EOF或错误,行读取而未截断,部分行读取但数据被截断。

This problem doesn't arise with gets() because it doesn't know where your buffer ends and merrily tramples beyond the end, wreaking havoc on your beautifully tended memory layout, often messing up the return stack (a Stack Overflow) if the buffer is allocated on the stack, or trampling over the control information if the buffer is dynamically allocated, or copying data over other precious global (or module) variables if the buffer is statically allocated. None of these is a good idea — they epitomize the phrase 'undefined behaviour`.


还有TR 24731-1(来自C标准委员会的技术报告),它为各种函数提供了更安全的替代方案,包括gets():

§6.5.4.1 gets_s函数

# # #剧情简介

#定义__STDC_WANT_LIB_EXT1__ # include < stdio . h > (Char *s, rsize_t n);

Runtime-constraints S不能是空指针。n既不等于零也不大于RSIZE_MAX。从stdin读取n-1个字符时,将出现换行符、文件结束符或读取错误。

3如果存在违反运行时约束的情况,s[0]将被设置为空字符,并且从stdin中读取并丢弃字符,直到读取换行字符,或文件结束或发生读取错误。

描述

gets_s函数从stdin指向的流读入s指向的数组的字符数最多比n少1个。在换行字符(被丢弃)之后或文件结束之后不读入额外的字符。丢弃的换行字符不计入读取的字符数。空字符在最后一个字符读入数组后立即写入。

5如果遇到文件结束符并且没有字符被读入数组,或者在操作过程中发生了读错误,那么s[0]将被设置为空字符,s的其他元素取不指定的值。

推荐的做法

fgets函数允许正确编写的程序安全地处理太长而不能存储在结果数组中的输入行。通常,这要求fget的调用者注意结果数组中是否存在换行字符。考虑使用fget(以及任何基于换行符的必要处理)而不是gets_s。

25) gets_s函数与gets函数不同,它使输入行溢出缓冲区来存储它违反了运行时约束。与fgets不同,gets_s在输入行和成功调用gets_s之间维护一对一的关系。使用gets的程序期望这样的关系。

Microsoft Visual Studio编译器实现了近似于TR 24731-1标准的签名,但是Microsoft实现的签名与TR中实现的签名之间存在差异。

C11标准,ISO/IEC 9899-2011,在附录K中包含TR24731作为库的可选部分。不幸的是,它很少在类unix系统上实现。


poxy(琼斯

POSIX 2008 also provides a safe alternative to gets() called getline(). It allocates space for the line dynamically, so you end up needing to free it. It removes the limitation on line length, therefore. It also returns the length of the data that was read, or -1 (and not EOF!), which means that null bytes in the input can be handled reliably. There is also a 'choose your own single-character delimiter' variation called getdelim(); this can be useful if you are dealing with the output from find -print0 where the ends of the file names are marked with an ASCII NUL '\0' character, for example.

其他回答

您不应该使用gets,因为它无法阻止缓冲区溢出。如果用户输入的数据超过了缓冲区的容量,您很可能会出现损坏或更糟的情况。

事实上,ISO实际上已经从C标准中删除了get(从C11开始,尽管它在C99中被弃用),考虑到他们对向后兼容性的高度评价,这应该表明该函数有多糟糕。

正确的做法是将fgets函数与stdin文件句柄一起使用,因为您可以限制从用户读取的字符。

但这也有它的问题,如:

用户输入的额外字符将在下次读取。 没有用户输入过多数据的快速通知。

为此,几乎每一个C程序员在他们职业生涯的某个阶段都会编写一个更有用的fget包装器。这是我的:

#include <stdio.h>
#include <string.h>

#define OK       0
#define NO_INPUT 1
#define TOO_LONG 2
static int getLine (char *prmpt, char *buff, size_t sz) {
    int ch, extra;

    // Get line with buffer overrun protection.
    if (prmpt != NULL) {
        printf ("%s", prmpt);
        fflush (stdout);
    }
    if (fgets (buff, sz, stdin) == NULL)
        return NO_INPUT;

    // If it was too long, there'll be no newline. In that case, we flush
    // to end of line so that excess doesn't affect the next call.
    if (buff[strlen(buff)-1] != '\n') {
        extra = 0;
        while (((ch = getchar()) != '\n') && (ch != EOF))
            extra = 1;
        return (extra == 1) ? TOO_LONG : OK;
    }

    // Otherwise remove newline and give string back to caller.
    buff[strlen(buff)-1] = '\0';
    return OK;
}

用一些测试代码:

// Test program for getLine().

int main (void) {
    int rc;
    char buff[10];

    rc = getLine ("Enter string> ", buff, sizeof(buff));
    if (rc == NO_INPUT) {
        printf ("No input\n");
        return 1;
    }

    if (rc == TOO_LONG) {
        printf ("Input too long\n");
        return 1;
    }

    printf ("OK [%s]\n", buff);

    return 0;
}

它提供了与fgets相同的保护,它可以防止缓冲区溢出,但它也通知调用者发生了什么,并清除多余的字符,以便它们不会影响您的下一个输入操作。

随心所欲地使用它,我在此发布它的“做你该死的想做的事情”许可证:-)

为了安全地使用get,您必须确切地知道将要读取多少字符,以便使缓冲区足够大。只有当您确切地知道要读取哪些数据时,您才会知道这一点。

您希望使用具有签名的fgets,而不是使用gets

char* fgets(char *string, int length, FILE * stream);

(fgets,如果它读取了整行,将在字符串中留下'\n';你得自己处理。)

直到1999年的ISO C标准,gets仍然是语言的官方部分,但在2011年的标准中被正式删除。大多数C实现仍然支持它,但至少gcc会对任何使用它的代码发出警告。

C语言的gets函数是危险的,是一个代价非常高昂的错误。Tony Hoare在他的演讲“Null References: The Billion Dollar Mistake”中特别提到了这一点:

http://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare

整个小时都值得一看,但他从30分钟开始的评论观点在39分钟左右受到了批评。

希望这能激发你对整个演讲的兴趣,让你注意到我们如何需要语言中更正式的正确性证明,以及语言设计者如何应该为他们语言中的错误而受到指责,而不是程序员。这似乎是糟糕语言的设计者打着“程序员自由”的幌子把责任推给程序员的全部可疑原因。

在C11(ISO/IEC 9899:201x)中,gets()已被删除。(在ISO/IEC 9899:1999/ co .3:2007(E)中已弃用)

除了fgets(), C11引入了一个新的安全替代gets_s():

C11 K.3.5.4.1 gets_s函数 #定义__STDC_WANT_LIB_EXT1__ # include < stdio . h > (Char *s, rsize_t n);

但是,在推荐实践部分中,fgets()仍然是首选。

fgets函数也允许正确编写的程序安全地处理输入行 在结果数组中存储。一般来说,这要求打电话的人得到报酬 注意结果数组中是否存在换行字符。考虑 使用fget(以及任何基于换行符的必要处理)而不是 gets_s。

我想向仍然在库中包含get的C库维护者发出诚挚的邀请,“以防有人仍然依赖它”:请将您的实现替换为

char *gets(char *str)
{
    strcpy(str, "Never use gets!");
    return str;
}

这将有助于确保没有人仍然依赖它。谢谢你!