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


144

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开始删除,这本身就是不好的。

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

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


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


请注意,stdin在标准库中为FILE*,并且任何类型的指针都将转换为char*,这是的参数类型gets()。但是,永远不要在混乱的C竞赛之外编写此类代码。如果您的编译器甚至接受它,请添加更多警告标志,并且如果您要修复其中包含该构造的代码库,请将警告变为错误。
戴维斯洛

1
@Davislor不,它不是“候选函数不可行:第一个参数没有从'struct _IO_FILE *'到'char *'的已知转换”
bolov

3
@Davislor呵呵,这对于古代C可能是正确的,但对于C ++绝对不是。
昆汀

@Quentin是的。那不应该编译。预期的挑战可能是,“拿下这个破损的代码,让我了解一下它应该做什么,并加以解决”,但是在那种情况下,应该有一个真正的规范。带有测试用例。
戴维斯洛

6
令人惊讶的是没有人尝试过这种方法,但是gets(stdin )(有额外的空间)会产生预期的C ++错误。
罗曼·奥戴斯基

Answers:


174

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

关于此帖子的评论是正确的,因为它是一种查找和替换的形式,所以让我解释一下为什么我真的很快做到了。

在我最初创建网站的那天(大约2012年),它仅支持JavaScript。没有办法在浏览器中运行的JavaScript中“读取输入”,因此会有一个函数foo(input),我使用readline()Node.js中的函数像那样调用它foo(readline())。除了我还很小的时候,我并不了解,所以从字面上看,我只是readline()在运行时替换了输入内容。因此foo(readline())成为JavaScript foo(2)foo("hello")效果良好的JavaScript。

在2013/2014年左右,我添加了更多的语言并使用了第三方服务来在线评估代码,但是很难对正在使用的服务执行stdin / stdout,因此我坚持使用相同的语言傻瓜式查找和替换例如Python,Ruby,最后是C ++,C#等。

时至今日,我在自己的容器中运行代码,但从未更新stdin / stdout的工作方式,因为人们已经习惯了怪异的黑客行为(有些人甚至在论坛上发布了解释如何解决它的信息)。

我知道这不是最佳实践,对于学习一种新语言的人来说,看到这样的黑客行为是无济于事的,但是这个想法是让新程序员完全不用担心阅读输入,而只专注于编写解决问题的算法。问题。几年前,有关编码挑战站点的一个普遍抱怨是,新程序员会花大量时间只是想弄清楚如何stdin从文件中读取行或从文件中读取行,因此我希望新的编码器在Coderbyte上避免此问题。

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


20
“但是,这个想法是让新程序员完全不用担心阅读输入,而只是专注于编写解决问题的算法”-并不是您想到的,而是编写类似于“真实的东西”的东西。 ”代码,只是在该位置放置一个组合函数名称或明显的占位符?真是好奇。
Ruther Rendommeleigh

25
我真的没想到我在发布此答案时会选择一个答案,而不是我自己的答案。感谢您以如此出色的方式证明我做错了。看到您的回答真的很高兴。
bolov

4
很有意思!我建议,如果您想保留此技巧,请将函数调用替换为TAKE_INPUT,然后使用find-replace插入#define TAKE_INPUT whatever_here到顶部。
Draconis

18
我们需要从“我是x的创始人,也是创建它的人”开始的更多答案。

2
@iheanyi没有人要求它是完美的。实际上,我坚信几乎所有占位符都会比看起来对任何新手有效的代码但实际上没有编译的占位符更好。
Ruther Rendommeleigh

112

我很感兴趣 因此,是时候放下调查镜了,并且由于我无权访问编译器或编译标志,因此需要发挥创造力。另外,由于此代码没有任何意义,因此对每个假设都提出质疑并不是一个坏主意。

首先,让我们检查的实际类型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)在实际将其输入编译器之前为测试用例的输入值。如果有人有更好的理论或实际上知道他们在做什么,请分享!

这显然是非常糟糕的做法。在研究这个问题时,我发现这里至少有一个问题(示例),因为人们不知道那里有一个站点可以这样做,所以他们的回答是“不要使用getsuse ...代替”,这的确是一个很好的建议,但只会使OP更加混乱,因为任何从stdin进行有效读取的尝试都将在此站点上失败。


TLDR

gets(stdin)无效的C ++。这个特定网站使用的是一个头(出于什么原因我无法弄清楚)。如果要继续在网站上提交(我既不认可也不不认可),则必须使用此结构,否则该结构将无济于事,但请注意它很脆弱。几乎所有对的修改main都会吐出一个错误。在此站点之外,请使用常规的输入阅读方法。


27
我真的很惊讶。也许这个Q / A可以成为关于为什么不向挑战网站编码学习的经典文章。
改变了igel

28
确实发生了一些邪恶的事情,我认为这是在编译器之外的源代码中的文本替换级别。试试看:std::cout << "gets(stdin)";输出是8(或您在“输入”字段中键入的任何内容。这是对语言的侮辱。)
igel改变19年

14
@Stobor请注意周围的引号"gets(stdin)"。那是一个字符串字面量,连预处理程序也不会碰到
改变了igel

2
引用詹姆斯·柯克的话:“这真该死。”
接近

2
@alterigel下车。这不是从编码挑战站点学习是否有用的声明。您是谁来决定人们如何练习事物?
Matsemann '19

66

main在Coderbyte编辑器中尝试了以下功能:

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

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

8

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

魔术码

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

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

我敢肯定,这不可能是这个很难只使用std::cin,只是流输入到程序。


而且它甚至不是盲目的“查找并替换”的,因为有时它会替换它,有时却不是。
bolov

4
@bolov可能只是gets(stdin)被替换的第一次出现?我的意思是“盲目的”,因为它似乎并不了解该语言的语法或语法。
改变了igel

是的,你是对的。它代替了第一次出现。我试过在main之前放一个,这确实是我得到的。
bolov

1
进一步的研究表明,该站点可以对所有语言进行操作,而不仅限于C ++-python / ruby​​使用了函数调用(“ raw_input()”或“ STDIN.gets”),该函数通常会从stdin返回一个字符串,但最终会这样做该字符串的字符串替换。我猜想找到一个与getline函数匹配的正则表达式太难了,所以他们在C / C ++中使用了gets(stdin)。
Stobor

4
@Stobor dang,您是对的。我也可以确认这种情况也发生在Java中,即使未定义,该行也会System.out.print(FirstFactorial(s.nextLine()9));打印。89s
改变igel
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.