当我尝试用GCC编译使用gets()函数的C代码时,我得到这个警告:
(.text+0x34):警告:' gets'函数是危险的,不应该使用。
我记得这与堆栈保护和安全性有关,但我不确定确切的原因。
我如何删除这个警告,为什么使用gets()会有这样的警告?
如果gets()是如此危险,那么为什么我们不能删除它?
当我尝试用GCC编译使用gets()函数的C代码时,我得到这个警告:
(.text+0x34):警告:' gets'函数是危险的,不应该使用。
我记得这与堆栈保护和安全性有关,但我不确定确切的原因。
我如何删除这个警告,为什么使用gets()会有这样的警告?
如果gets()是如此危险,那么为什么我们不能删除它?
为了安全地使用get,您必须确切地知道将要读取多少字符,以便使缓冲区足够大。只有当您确切地知道要读取哪些数据时,您才会知道这一点。
您希望使用具有签名的fgets,而不是使用gets
char* fgets(char *string, int length, FILE * stream);
(fgets,如果它读取了整行,将在字符串中留下'\n';你得自己处理。)
直到1999年的ISO C标准,gets仍然是语言的官方部分,但在2011年的标准中被正式删除。大多数C实现仍然支持它,但至少gcc会对任何使用它的代码发出警告。
如果不破坏API,就不能删除API函数。如果您这样做,许多应用程序将不再编译或运行。
这就是一篇参考文章给出的原因:
读取溢出的行 由s指向的数组结果为 未定义的行为。fgets()的使用 建议。
因为gets在从stdin获取字节并将它们放到某个地方时不会做任何检查。举个简单的例子:
char array1[] = "12345";
char array2[] = "67890";
gets(array1);
首先,你可以输入你想要的字符数,get不会关心它。其次,字节超过数组的大小(在本例中为array1)将覆盖它们在内存中找到的任何内容,因为gets将写入它们。在前面的例子中,这意味着如果你输入"abcdefghijklmnopqrts"可能,不可预知的,它也会覆盖array2或其他东西。
该函数是不安全的,因为它假定输入一致。永远不要用它!
我最近在comp.lang.c的一篇USENET文章中读到,gets()将从标准中删除。哦吼
你会很高兴知道 委员会刚刚投票(一致通过) 将gets()从 还有草案。
fgets。
从stdin读取:
char string[512];
fgets(string, sizeof(string), stdin); /* no buffer overflows here, you're safe! */
为什么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相同的保护,它可以防止缓冲区溢出,但它也通知调用者发生了什么,并清除多余的字符,以便它们不会影响您的下一个输入操作。
随心所欲地使用它,我在此发布它的“做你该死的想做的事情”许可证:-)
在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;
}
这将有助于确保没有人仍然依赖它。谢谢你!
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分钟左右受到了批评。
希望这能激发你对整个演讲的兴趣,让你注意到我们如何需要语言中更正式的正确性证明,以及语言设计者如何应该为他们语言中的错误而受到指责,而不是程序员。这似乎是糟糕语言的设计者打着“程序员自由”的幌子把责任推给程序员的全部可疑原因。
gets() is dangerous because it is possible for the user to crash the program by typing too much into the prompt. It can't detect the end of available memory, so if you allocate an amount of memory too small for the purpose, it can cause a seg fault and crash. Sometimes it seems very unlikely that a user will type 1000 letters into a prompt meant for a person's name, but as programmers, we need to make our programs bulletproof. (it may also be a security risk if a user can crash a system program by sending too much data).
Fgets()允许您指定从标准输入缓冲区中取出多少字符,以便它们不会溢出变量。
额外的信息:
从man 3得到的Linux Ubuntu你会看到(强调):
描述 永远不要使用这个函数。
并且,从cppreference.com wiki这里(https://en.cppreference.com/w/c/io/gets)你会看到:Notes从不使用gets()。
笔记 gets()函数不执行边界检查,因此这个函数非常容易受到缓冲区溢出攻击。它不能安全使用(除非程序运行在限制stdin上可以显示内容的环境中)。因此,该函数在C99标准的第三个勘误表中已被弃用,并在C11标准中被完全删除。Fgets()和gets_s()是推荐的替换。 永远不要使用gets()。
如您所见,该函数在C11或更高版本中已完全弃用并被删除。
请使用fgets()或gets_s()。
下面是我使用fgets()的演示,带有完整的错误检查:
从read_stdin_fgets_basic_input_from_user.c:
#include <errno.h> // `errno`
#include <stdio.h> // `printf()`, `fgets()`
#include <stdlib.h> // `exit()`
#include <string.h> // `strerror()`
// int main(int argc, char *argv[]) // alternative prototype
int main()
{
char buf[10];
// NEVER USE `gets()`! USE `fgets()` BELOW INSTEAD!
// USE THIS!: `fgets()`: "file get string", which reads until either EOF is
// reached, OR a newline (`\n`) is found, keeping the newline char in
// `buf`.
// For `feof()` and `ferror()`, see:
// 1. https://en.cppreference.com/w/c/io/feof
// 1. https://en.cppreference.com/w/c/io/ferror
printf("Enter up to %zu chars: ", sizeof(buf) - 1); // - 1 to save room
// for null terminator
char* retval = fgets(buf, sizeof(buf), stdin);
if (feof(stdin))
{
// Check for `EOF`, which means "End of File was reached".
// - This doesn't really make sense on `stdin` I think, but it is a good
// check to have when reading from a regular file with `fgets
// ()`. Keep it here regardless, just in case.
printf("EOF (End of File) reached.\n");
}
if (ferror(stdin))
{
printf("Error indicator set. IO error when reading from file "
"`stdin`.\n");
}
if (retval == NULL)
{
printf("ERROR in %s(): fgets() failed; errno = %i: %s\n",
__func__, errno, strerror(errno));
exit(EXIT_FAILURE);
}
size_t num_chars_written = strlen(buf) + 1; // + 1 for null terminator
if (num_chars_written >= sizeof(buf))
{
printf("Warning: user input may have been truncated! All %zu chars "
"were written into buffer.\n", num_chars_written);
}
printf("You entered \"%s\".\n", buf);
return 0;
}
示例运行和输出:
eRCaGuy_hello_world/c$ gcc -Wall -Wextra -Werror -O3 -std=c17 read_stdin_fgets_basic_input_from_user.c -o bin/a && bin/a
Enter up to 9 chars: hello world!
Warning: user input may have been truncated! All 10 chars were written into buffer.
You entered "hello wor".
eRCaGuy_hello_world/c$ gcc -Wall -Wextra -Werror -O3 -std=c17 read_stdin_fgets_basic_input_from_user.c -o bin/a && bin/a
Enter up to 9 chars: hey
You entered "hey
".