站点 coderbyte 上的‘ gets (stdin)’是怎么回事?

Coderbyte 是一个在线编码挑战站点(我2分钟前才发现它)。

你遇到的第一个 C + + 挑战 有一个需要修改的 C + + 框架:

#include <iostream>
#include <string>
using namespace std;


int FirstFactorial(int num) {


// Code goes here
return num;


}


int main() {


// Keep this function call here
cout << FirstFactorial(gets(stdin));
return 0;


}

如果你对 C + + 不太熟悉,那么 *第一个出现在你眼前的就是:

int FirstFactorial(int num);
cout << FirstFactorial(gets(stdin));

好的,代码调用 gets,这在 C + + 11之后就被废弃了,在 C + + 14之后被删除了,这本身就很糟糕。

但后来我意识到: getschar*(char*)型。因此,它不应该接受 FILE*参数,结果也不应该用于取代 int参数,但是... ... 它不仅编译时没有任何警告或错误,而且它运行时实际上将正确的输入值传递给 FirstFactorial

在这个特定的站点之外,代码不能编译(正如预期的那样) ,那么这里发生了什么?


* 其实第一个是 using namespace std但这和我的问题无关。

8970 次浏览

我很好奇。所以,是时候戴上调查眼镜了,因为我没有访问编译器或编译标志的权限,所以我需要发明创造。同时,因为这段代码没有任何意义,所以质疑每一个假设并不是一个坏主意。

首先让我们检查一下 gets的实际类型。我有一个小窍门:

template <class> struct Name;


int main() {
    

Name<decltype(gets)> n;
  

// keep this function call here
cout << FirstFactorial(gets(stdin));
return 0;
    

}

这看起来... 很正常:

/tmp/613814454/Main.cpp:16:19: warning: 'gets' is deprecated [-Wdeprecated-declarations]
Name<decltype(gets)> n;
^
/usr/include/stdio.h:638:37: note: 'gets' has been explicitly marked deprecated here
extern char *gets (char *__s) __wur __attribute_deprecated__;
^
/usr/include/x86_64-linux-gnu/sys/cdefs.h:254:51: note: expanded from macro '__attribute_deprecated__'
# define __attribute_deprecated__ __attribute__ ((__deprecated__))
^
/tmp/613814454/Main.cpp:16:26: error: implicit instantiation of undefined template 'Name<char *(char *)>'
Name<decltype(gets)> n;
^
/tmp/613814454/Main.cpp:12:25: note: template is declared here
template <class> struct Name;
^
1 warning and 1 error generated.

gets被标记为弃用,并且有 char *(char *)的签名。那么 FirstFactorial(gets(stdin));是如何编译的呢?

我们试试别的:

int main() {
Name<decltype(gets(stdin))> n;
  

// keep this function call here
cout << FirstFactorial(gets(stdin));
return 0;
    

}

这就给了我们:

/tmp/286775780/Main.cpp:15:21: error: implicit instantiation of undefined template 'Name<int>'
Name<decltype(8)> n;
^

最后我们得到了一些东西: decltype(8)。所以整个 gets(stdin)在文本上被输入(8)所替代。

事情变得更奇怪了,编译器的错误还在继续:

/tmp/596773533/Main.cpp:18:26: error: no matching function for call to 'gets'
cout << FirstFactorial(gets(stdin));
^~~~
/usr/include/stdio.h:638:14: note: candidate function not viable: no known conversion from 'struct _IO_FILE *' to 'char *' for 1st argument
extern char *gets (char *__s) __wur __attribute_deprecated__;

现在我们得到了 cout << FirstFactorial(gets(stdin));的预期误差

我检查了一个宏,因为 #undef gets似乎没有做什么,它看起来不像是一个宏。

但是

std::integral_constant<int, gets(stdin)> n;

它会编译。

但是

std::integral_constant<int, gets(stdin)> n;    // OK
std::integral_constant<int, gets(stdin)> n2;   // ERROR                                          wtf??

n2行没有出现预期的错误。

同样,几乎对 main的任何修改都会使 cout << FirstFactorial(gets(stdin));行显示出预期的错误。

此外,stdin实际上似乎是空的。

所以我只能得出这样的结论: 他们有一个小程序,解析源代码,并且在实际将测试用例输入值输入到编译器之前,尝试(很糟糕地)将 gets(stdin)替换为测试用例输入值。如果有人有更好的理论或者实际知道他们在做什么,请分享!

这显然是一个非常糟糕的做法。在研究这个问题的时候,我发现这里至少有一个问题(例子) ,因为人们不知道有一个网站这样做,他们的答案是“不要使用 gets使用... 相反”,这确实是一个很好的建议,但只是混淆了 OP,因为任何尝试从 stdin 有效读取将失败在这个网站上。


TLDR

gets(stdin)是无效的 C + + 。这是这个网站使用的一个噱头(我不知道是什么原因)。如果你想继续在网站上提交(我既没有认可它,也没有认可它) ,你必须使用这个结构,否则不会有意义,但要知道它是脆弱的。几乎所有对 main的修改都会显示一个错误。在本网站之外使用普通的输入读取方法。

我在 Coderbyte 编辑器中尝试了以下对 main的添加:

std::cout << "gets(stdin)";

其中神秘而神秘的代码片段 gets(stdin)出现在字符串文字中。这不可能被任何东西转换,甚至不能被预处理器转换,而且 任何 C + + 程序员应该期望这段代码将确切的字符串 gets(stdin)打印到标准输出。然而,当编译并在 coderbyte 上运行时,我们可以看到以下输出:

8

其中值 8直接取自编辑器下方便的“输入”字段。

Magic code

由此可见,这个在线编辑器正在对源代码执行盲查找和替换操作,用用户的“输入”替换 gets(stdin)的外观。我个人认为这是对语言的误用,比粗心的预处理宏更糟糕。

在一个在线编码挑战网站的背景下,我对此感到担忧,因为它教授非常规的,非标准的,毫无意义的,至少像 gets(stdin)这样的 不安全实践,而且是以一种在其他平台上无法重复的方式。

我敢肯定,仅仅使用 std::cin并对程序进行流输入不会很难。

我是 Coderbyte 的创始人也是这个 gets(stdin)黑客的创始人。

这篇文章的评论是正确的,它是一种查找和替换的形式,所以让我解释为什么我这样做真的很快。

在我第一次创建这个网站的时候(大约在2012年) ,它只支持 JavaScript。在浏览器中运行的 JavaScript 无法“读入输入”,因此会有一个函数 foo(input),我使用 Node.js 中的 readline()函数来调用它,就像 foo(readline())一样。只不过我当时还是个孩子,不知道该怎么做,所以我只是在运行时用输入替换了 readline()。所以 foo(readline())变成了 foo(2)或者 foo("hello"),这对 JavaScript 来说很好用。

大约在2013/2014年,我添加了更多的语言,并使用第三方服务来评估在线代码,但是我使用的服务很难使用 stdin/stdout,所以我坚持使用同样愚蠢的查找和替换语言,比如 Python、 Ruby,最终是 C + + 、 C # 等等。

快进到今天,我在自己的容器中运行代码,但从未更新 stdin/stdout 的工作方式,因为人们已经习惯了这种奇怪的黑客行为(有些人甚至在论坛上发帖解释如何绕过它)。

我知道这不是最佳实践,对于学习一门新语言的人来说,看到这样的黑客技巧是没有帮助的,但这个想法是为了让新程序员根本不用担心读取输入,而只专注于编写解决问题的算法。几年前,编码挑战站点的一个常见抱怨是,新程序员会花费大量时间来弄清楚如何从 stdin读取或从文件中读取行,所以我希望新程序员在 Coderbyte 上避免这个问题。

我将很快更新整个编辑器页面以及语言的默认代码和 stdin阅读。希望 C + + 程序员会更喜欢使用 Coderbyte:)