Coderbyte是一个在线编码挑战网站(我在2分钟前发现它)。
你会遇到The first C++ challenge有一个你需要修改的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以来被删除,这本身就很糟糕。
但后来我意识到:gets
的类型是char*(char*)
。所以它不应该接受FILE*
参数,并且结果不应该在int
参数的位置使用,但是...不仅它编译时没有任何警告或错误,但它运行并实际传递正确的输入值FirstFactorial
。
在这个特定站点之外,代码没有编译(如预期的那样),那么这里发生了什么?
*实际上第一个是using namespace std
,但这与我的问题无关。
我是Coderbyte的创始人,也是创建这个gets(stdin)
hack的人。
这篇文章的评论是正确的,它是一种查找和替换的形式,所以让我解释为什么我这么快就做到了。
早在我第一次创建网站的那天(大约在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 :)
我很好奇。所以,是时候把调查护目镜放在上面,因为我无法访问编译器或编译标志,我需要有所创新。此外,因为这个代码没有任何意义,所以每个假设都不是一个坏主意。
首先让我们检查一下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)
替换测试用例输入值,然后再将其输入编译器。如果有人有更好的理论或实际知道他们在做什么,请分享!
这显然是一种非常糟糕的做法。在研究这个时,我发现这里至少有一个问题(example),因为人们不知道有一个网站在那里做这个,他们的回答是“不要使用gets
使用......而是”这是确实是一个很好的建议,但只会混淆OP,因为任何有效读取stdin的尝试都将在此站点上失败。
gets(stdin)
是无效的C ++。这是一个特殊网站使用的噱头(出于什么原因,我无法弄清楚)。如果你想继续在网站上提交(我既不支持它也不支持它)你必须使用这个没有意义的结构,但要注意它是脆弱的。几乎任何对main
的修改都会吐出错误。在本网站之外使用正常的输入阅读方法。
我在Coderbyte编辑器中尝试了以下对main
的补充:
std::cout << "gets(stdin)";
神秘而神秘的片段gets(stdin)
出现在字符串文字中。这不应该由任何东西改变,甚至不是预处理器,并且任何C ++程序员都应该期望这段代码将精确的字符串gets(stdin)
打印到标准输出。然而,当编译并在coderbyte上运行时,我们会看到以下输出:
8
值8
直接取自编辑器下方便的“输入”字段。
由此可见,这个在线编辑器正在对源代码执行盲目查找和替换操作,用用户的“输入”替换gets(stdin)
的外观。我个人称这是滥用语言,而不是粗心的预处理器宏。
在线编码挑战网站的背景下,我很担心这一点,因为它教授非常规的,非标准的,毫无意义的,至少是不安全的做法,如gets(stdin)
,以及在其他平台上无法重复的方式。
我敢肯定,使用std::cin
并将输入流式传输到程序中并不难。