网站 coderbyte 上的 'gets(stdin)' 是怎么回事?

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

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 起已删除,这本身就很糟糕。

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

在这个特定站点之外,代码无法编译(如预期),那么这是怎么回事?


*实际上第一个是using namespace std,但这与我这里的问题无关。

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

首先让我们检查一下 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 有效读取的尝试都会在此站点上失败。


TLDR

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

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

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

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

8

8 直接取自编辑器下方方便的 'input' 字段。

由此可见,该在线编辑器正在对源代码进行盲目查找和替换操作,将出现的gets(stdin)替换为用户的'input'。我个人认为这是对语言的滥用,比粗心的预处理器宏更糟糕。

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

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

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

关于这个 post 的评论是正确的,它是 find-and-replace 的一种形式,所以让我解释一下为什么我很快就这样做了。

在我第一次创建网站的那一天(2012 年左右),它只支持 JavaScript。 "read in input" in JavaScript 运行 无法在浏览器中使用,因此会有一个函数 foo(input) 并且我使用了 readline() 中的函数 Node.js 可以像 foo(readline()) 那样称呼它。除非我是个孩子,不知道更多,所以我只是用 run-time 处的输入替换了 readline()。所以 foo(readline()) 变成了 foo(2)foo("hello") 这对 JavaScript.

很好用

2013/2014 年左右,我添加了更多语言并使用 third-party 服务在线评估代码,但是 stdin/stdout 使用我使用的服务非常困难,所以我坚持使用对于 Python、Ruby 以及最终的 C++、C# 等语言,同样愚蠢的 find-and-replace

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

我知道这不是最佳实践,对于学习一门新语言的人看到这样的 hack 也无济于事,但我的想法是让新程序员根本不用担心阅读输入,只需专注于写作解决问题的算法。多年前对编码挑战网站的一个普遍抱怨是,新程序员会花费大量时间来弄清楚如何从 stdin 读取或从文件中读取行,所以我希望新编码员在 Coderbyte 上避免这个问题。

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