【问题标题】:What is going on with 'gets(stdin)' on the site coderbyte?网站 coderbyte 上的“gets(stdin)”是怎么回事?
【发布时间】:2019-08-11 15:33:13
【问题描述】:

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,但这与我的问题无关。

【问题讨论】:

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

标签: c++ input gets standards-compliance


【解决方案1】:

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

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

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

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

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

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

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

【讨论】:

  • "[B]但是这个想法是让新程序员根本不用担心读取输入,而只专注于编写算法来解决问题" - 你没有想到,而不是编写类似于“真实”代码的东西,只需在该位置放置一个虚构的函数名称或一个明显的占位符?真的很好奇。
  • 当我发布这篇文章时,我真的没想到我会选择我自己的答案。感谢您以如此出色的方式证明我错了。很高兴看到你的回答。
  • 非常有趣!如果您想保留此技巧,我建议您将函数调用替换为 TAKE_INPUT 之类的内容,然后使用 find-replace 在顶部插入 #define TAKE_INPUT whatever_here
  • 我们需要更多从 “我是 x 的创始人,也是创造这个的人”开始的答案
  • @iheanyi 没有人要求它是完美的。事实上,我相信几乎任何占位符都会比那些看起来对任何新手来说都是有效代码但实际上并不能编译的东西要好。
【解决方案2】:

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

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

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

8

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

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

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

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

【讨论】:

  • 它甚至不是盲目的“查找和替换”,因为有时它会替换它,有时它不会。
  • @bolov 会不会是 gets(stdin) 的第一次出现被替换?我的意思是“盲目”,因为它似乎不知道该语言的句法或语法。
  • 是的,你是对的。它取代了第一次出现。我试着在 main 之前放一个,这确实是我得到的。
  • 进一步的研究表明,该站点对所有语言都执行此操作,而不仅仅是 C++ - python/ruby 它使用通常会返回一个函数调用(“raw_input()”或“STDIN.gets”)来自标准输入的字符串,但最终改为对该字符串进行字符串替换。我猜想为 getline 函数找到一个正则表达式匹配太难了,所以他们选择了 C/C++ 的 gets(stdin)。
  • @Stobor dang,你是对的。我可以确认 Java 也会发生这种情况,即使 s 未定义,System.out.print(FirstFactorial(s.nextLine()9)); 行也会打印 89
【解决方案3】:

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

首先让我们检查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 &lt;&lt; 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 &lt;&lt; FirstFactorial(gets(stdin)); 行吐出预期的错误。

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

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

这显然是一个非常糟糕的做法。在研究这个问题时,我发现这里至少有一个问题(example),因为人们不知道有一个网站可以做到这一点,他们的答案是“不要使用gets使用......相反”这确实是一个很好的建议,但只会让 OP 更加困惑,因为任何从标准输入进行有效读取的尝试都将在此站点上失败。


TLDR

gets(stdin) 是无效的 C++。这是这个特定网站使用的噱头(我不知道是什么原因)。如果您想继续在网站上提交(我既不认可也不认可),您必须使用此构造,否则将没有意义,但请注意它很脆弱。几乎对main 的任何修改都会产生错误。在本网站之外使用正常的输入阅读方法。

【讨论】:

  • 我真的很惊讶。也许这个 Q/A 可以成为一篇关于为什么不向编码挑战网站学习的规范帖子。
  • 真正邪恶的事情正在发生,我认为这是在编译器之外的源代码中的文本替换级别。试试这个:std::cout &lt;&lt; "gets(stdin)";,输出是8(或者你在“输入”字段中输入的任何内容。这是对语言的可耻滥用。
  • @Stobor 注意"gets(stdin)" 周围的引号。这是一个即使预处理器也不会触及的字符串文字
  • 引用 James Kirk 的话:“这该死的奇特。”
  • @alterigel 下车。这并不是说从编码挑战网站学习是否有用。你是谁来决定人们如何练习?
猜你喜欢
  • 1970-01-01
  • 2011-07-21
  • 1970-01-01
  • 1970-01-01
  • 2017-10-20
  • 2013-07-06
  • 2012-03-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多