站点 coderbyte 上的“gets(stdin)”是怎么回事?

What is going on with 'gets(stdin)' on the site coderbyte?

提问人:bolov 提问时间:3/21/2019 最后编辑:Peter Mortensenbolov 更新时间:3/28/2019 访问量:9267

问:

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));

所以,好的,代码调用自 C++11 以来已弃用,自 C++14 以来已删除,这本身就很糟糕。gets

但后来我意识到:是类型.所以它不应该接受参数,结果不应该代替参数,但是......它不仅在没有任何警告或错误的情况下进行编译,而且运行并实际将正确的输入值传递给 。getschar*(char*)FILE*intFirstFactorial

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


*实际上第一个是使用命名空间 std,但这与我在这里的问题无关。

C++ 输入 符合 标准

评论

0赞 Davislor 3/21/2019
请注意,在标准库中有一个 ,指向任何类型的指针将转换为 ,这是 的参数类型。但是,你永远不应该在混淆的 C 竞赛之外编写这种代码。如果你的编译器甚至接受它,添加更多的警告标志,如果你试图修复包含该构造的代码库,请将警告变成错误。stdinFILE*char*gets()
1赞 bolov 3/21/2019
@Davislor不,它不会“候选函数不可行:第一个参数没有从'struct _IO_FILE *'到'char *'的已知转换”
3赞 Quentin 3/21/2019
@Davislor呵呵,这对古代 C 来说可能是正确的,但绝对不适用于C++。
0赞 Davislor 3/21/2019
@Quentin是的。这不应该编译。预期的挑战可能是,“接受这个损坏的代码,读懂我应该做什么,然后修复它”,但在这种情况下,应该有一个真正的规范。使用测试用例。
6赞 Roman Odaisky 3/21/2019
我很惊讶没有人尝试过这个,但是(有额外的空间)会产生预期的 C++ 错误。gets(stdin )

答:

113赞 bolov 3/21/2019 #1

我很感兴趣。所以,是时候戴上调查护目镜了,因为我无法访问编译器或编译标志,我需要发挥创造力。此外,由于此代码没有任何意义,因此质疑每个假设都不是一个坏主意。

首先,我们来检查一下 的实际类型。我有一个小窍门: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

同样,几乎任何使行的修改都会吐出预期的错误。maincout << FirstFactorial(gets(stdin));

而且,实际上似乎是空的。stdin

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

这显然是一种非常糟糕的做法。在研究这个问题时,我发现这里至少有一个关于这个问题的问题(示例),因为人们不知道有一个网站在那里这样做,他们的答案是“不要使用,使用......取而代之的是“这确实是一个很好的建议,但只会使 OP 更加困惑,因为任何从 stdin 有效读取的尝试都会在这个站点上失败。gets


顶级域名

gets(stdin)无效的 C++。这是这个特定网站使用的噱头(出于什么原因我无法弄清楚)。如果你想继续在网站上提交(我既不认可它,也不认可它),你必须使用这个结构,否则就没有意义,但要注意它是脆弱的。几乎任何修改都会吐出错误。在本网站之外,请使用正常的输入读取方法。main

评论

28赞 alter_igel 3/21/2019
我真的很惊讶。也许这个问答可以成为一篇关于为什么不从编码挑战网站学习的规范帖子。
28赞 alter_igel 3/21/2019
一些非常邪恶的事情正在发生,我认为这是在编译器之外的源代码中的文本替换级别。试试这个:输出是(或您在“输入”字段中输入的任何内容。这是对语言的可耻滥用。std::cout << "gets(stdin)";8
14赞 alter_igel 3/21/2019
@Stobor注意周围的引号。这是一个字符串文字,即使是预处理器也不会触及"gets(stdin)"
2赞 ApproachingDarknessFish 3/22/2019
引用詹姆斯·柯克(James Kirk)的话:“这太奇怪了。
2赞 Matsemann 3/22/2019
@alterigel从你的高头大马上下来。这并不是关于从编码挑战网站学习是否有用的陈述。你是谁来决定人们如何练习东西?
65赞 alter_igel 3/21/2019 #2

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

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

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

8

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

Magic code

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

在在线编码挑战网站的背景下,我对此感到担忧,因为它教授非常规、非标准、无意义且至少是不安全的做法,例如,并且以其他平台上无法重复的方式进行。gets(stdin)

我敢肯定,仅仅使用和将输入流式传输到程序不会这么难。std::cin

评论

0赞 bolov 3/21/2019
它甚至不是盲目的“查找和替换”,因为有时它会替换它,有时它不会。
4赞 alter_igel 3/21/2019
@bolov,它可能只是第一次被替换?我的意思是“盲目”,因为它似乎不知道语言的语法或语法。gets(stdin)
0赞 bolov 3/21/2019
是的,你是对的。它替换了第一次出现的情况。我试着把一个放在主之前,这就是我得到的。
1赞 Stobor 3/21/2019
进一步的研究表明,该网站适用于所有语言,而不仅仅是C++ - python/ruby 它使用函数调用(“raw_input()”或“STDIN.gets”),它通常会从 stdin 返回一个字符串,但最终会执行该字符串的字符串替换。我想为 getline 函数找到正则表达式匹配太难了,所以他们为 C/C++ 选择了 gets(stdin)。
4赞 alter_igel 3/21/2019
@Stobor,你是对的。我可以确认 Java 也会发生这种情况,即使未定义,该行也会打印。System.out.print(FirstFactorial(s.nextLine()9));89s
174赞 Daniel Borowski 3/21/2019 #3

我是 Coderbyte 的创始人,也是创建这个 hack 的人。gets(stdin)

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

当我第一次创建这个网站时(大约在2012年),它只支持JavaScript。在浏览器中运行的 JavaScript 中没有办法“读入输入”,因此会有一个函数,我使用 Node.js 中的函数来调用它,就像 .除了我还是个孩子,不知道更好,所以我实际上只是在运行时替换了输入。所以成为或对 JavaScript 工作得很好。foo(input)readline()foo(readline())readline()foo(readline())foo(2)foo("hello")

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

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

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

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

评论

22赞 Ruther Rendommeleigh 3/21/2019
“这个想法是让新程序员根本不担心读取输入,而只是专注于编写算法来解决问题”——你没有想到,不是编写类似于“真实”代码的东西,而是在那个位置放一个虚构的函数名称或一个明显的占位符?真的很好奇。
25赞 bolov 3/21/2019
我真的没想到,当我发布这篇文章时,我会选择一个不是我自己答案的答案。谢谢你以如此伟大的方式证明我错了。很高兴看到你的回答。
4赞 Draconis 3/22/2019
很有意思!我建议,如果你想保留这个技巧,你用类似的东西替换函数调用,然后使用你的find-replace插入到顶部。TAKE_INPUT#define TAKE_INPUT whatever_here
18赞 pipe 3/22/2019
我们需要更多答案,从“我是x的创始人,也是创造这个的人”开始。
2赞 Ruther Rendommeleigh 3/25/2019
@iheanyi 没有人要求它是完美的。事实上,我相信几乎任何占位符都比对任何新手来说看起来像有效代码但实际上没有编译的东西要好。