サイト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));

つまり、getsC ++ 11以降は非推奨であり、C ++ 14以降は削除されているコード自体が悪いコード呼び出しです。

しかし、私は気づきます:getsタイプchar*(char*)です。したがって、FILE*パラメータを受け入れてはならず、結果はパラメータの代わりに使用できませんint。警告やエラーなしでコンパイルされるだけでなく、実行され、実際に正しい入力値がに渡されFirstFactorialます。

この特定のサイト以外では、コードは(期待どおりに)コンパイルされないので、ここで何が起こっているのでしょうか。


*実際には最初のusing namespace std問題ですが、ここでの問題には関係ありません。


stdin標準ライブラリではFILE*であり、任意の型へのポインタchar*はの引数の型であるに変換されることに注意してくださいgets()。ただし、そのようなコードを難読化されたCコンテスト以外で書くことは決してありません。コンパイラがそれを受け入れる場合でも、警告フラグを追加します。その構成が含まれているコードベースを修正しようとしている場合は、警告をエラーに変換します。
Davislor

1
@Davislorいいえ、それはありません。「候補関数は実行できません:1番目の引数で 'struct _IO_FILE *'から 'char *'への既知の変換はありません」
bolov

3
@Davislorええと、それは古代のCには当てはまるかもしれませんが、C ++には当てはまりません。
クエンティン

@クエンティンうん。コンパイルしないでください。意図された課題は「この壊れたコードを取り、それが何をすべきかについて私の心を読んで、それを修正する」かもしれませんが、その場合、実際の仕様があるはずです。テストケース付き。
Davislor

6
誰もこれを試していないことに驚いていますが、gets(stdin )(余分なスペースがあると)予想されるC ++エラーが発生します。
Roman Odaisky

回答:


174

私はCoderbyteの創設者であり、このgets(stdin)ハックを作成した人でもあります。

この投稿へのコメントは、それが検索と置換の形式であることは正しいので、なぜこれを本当に迅速に行ったのかを説明しましょう。

私が最初にサイトを作成したとき(2012年頃)、JavaScriptのみをサポートしていました。ブラウザで実行されているJavaScriptで「入力を読み込む」方法がなかったためfoo(input)readline()関数があり、Node.js の関数を使用してのように呼び出しましたfoo(readline())。私が子供でよく分からなかった以外は、文字通りreadline()実行時に入力に置き換えられました。そうfoo(readline())なったfoo(2)foo("hello")はJavaScriptの罰金を働いています。

2013/2014年頃に言語を追加し、サードパーティのサービスを使用してコードをオンラインで評価しましたが、使用していたサービスでstdin / stdoutを実行することは非常に困難でした。 Python、Ruby、そして最終的にはC ++、C#など。

今日に早送りし、自分のコンテナでコードを実行しますが、奇妙なハックに慣れているため、stdin / stdoutの動作を更新していません(その方法を説明するフォーラムに投稿している人もいます)。

私はそれがベストプラクティスではなく、新しい言語を学ぶ人がこのようなハックを見るのは役に立たないことを知っていますが、そのアイデアは、新しいプログラマが入力の読み取りについてまったく心配することなく、アルゴリズムを書くことに集中して解決することでした問題。数年前のチャレンジサイトのコーディングに関する一般的な不満の1つは、新しいプログラマーがstdinファイルの読み取り方法やファイルからの行の読み取り方法を理解するのに多くの時間を費やすため、新しいコーダーがCoderbyteでこの問題を回避できるようにしたことです。

エディターのページ全体をデフォルトのコードと一緒にすぐに更新し、stdin言語を読みます。うまくいけば、C ++プログラマーはCoderbyteをもっと楽しんでいただけるでしょう:)


20
「しかし、このアイデアは、新しいプログラマーが入力の読み取りについてまったく心配することなく、問題を解決するためのアルゴリズムの作成に集中することでした」-そして、「本物」に似たものを書く代わりに、あなたはそれを思いつきませんでした"コード、その場所に構成された関数名または明白なプレースホルダーを置くだけですか?真に好奇心が強い。
Ruther Rendommeleigh

25
私がこれを投稿したときに、自分以外の答えを選ぶつもりであるとは本当に思っていませんでした。このような素晴らしい方法で私を誤解してくれてありがとう。あなたの答えを見ることは本当に嬉しいです。
bolov

4
とても興味深い!このハックを維持したい場合は、関数呼び出しをのようなものにTAKE_INPUT置き換え、find-replaceを使用#define TAKE_INPUT whatever_hereして上部に挿入することをお勧めします。
ドラコニス

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)実際にコンパイラに入力する前に、テストケースの入力値と(不十分に)置き換えようとする小さなプログラムがあると結論づけて推測することしかできません。誰かがより良い理論を持っているか、実際に彼らがやっていることを知っているなら、共有してください!

これは明らかに非常に悪い習慣です。これを調査しているときに、これについて少なくともここ()に質問があることを発見しました。これを行うサイトが存在することを人々が知らないため、gets実際には「使用しないでください... 」という答えが返ってきます。良いアドバイスですが、このサイトではstdinからの有効な読み取りの試みは失敗するため、OPをさらに混乱させるだけです。


TLDR

gets(stdin)無効なC ++です。これは、この特定のサイトで使用されている仕掛けです(理由がわからないため)。サイトへの提出を継続したい場合(私はそれを支持することも、支持することもしない)、この構文を使用する必要があります。これは、他の方法では意味がありませんが、壊れやすいことに注意してください。ほとんどすべての変更mainはエラーを吐き出します。このサイト以外では、通常の入力読み取り方法を使用してください。


27
私は本当に驚いています。たぶん、このQ / Aは、チャレンジサイトのコーディングから学ぶべきではないということについての正規の投稿になる可能性があります。
igelを

28
本当に悪いことが起こっており、コンパイラの外のソースコードでのテキスト置換のレベルにあると思います。これを試してください:std::cout << "gets(stdin)";出力は8(または「入力」フィールドに入力するものは何でも。これは言語の恥ずべき乱用です。)
igel

14
@Stoborの前後の引用符に注意してください"gets(stdin)"。これは文字列リテラルであり、プリプロセッサでさえ触れないでしょう
igelを変更してください

2
ジェームズ・カークを引用すると、「これはいまいましい奇妙なことです。」
へのアプローチ

2
@alterigelはあなたの高い馬を降りる。これは、コーディングチャレンジサイトから学ぶことが有用であるかどうかについての記述ではありません。人々がどのように物事を実践するかを決めるのは誰ですか?
Matsemann

66

mainCoderbyteエディターで次の追加を試しました:

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

神秘的で不可解なスニペットgets(stdin)が文字列リテラル内に表示される場所。これは、プリプロセッサでさえも、何によっても変換されるべきではありません。 C ++プログラマはこのコードが正確な文字列gets(stdin)を標準出力に出力することを期待する必要があります。それでも、コンパイルしてcoderbyteで実行すると、次の出力が表示されます。

8

どこに値 8は、エディターの下の便利な「入力」フィールドから直接取得されます。

マジックコード

このことから、このオンラインエディターがソースコードに対してブラインドの検索および置換操作を実行していることは明らかですgets(stdin)。私はこれを、不注意なプリプロセッサマクロよりも悪い言語の誤用と呼んでいます。

オンラインコーディングのチャレ​​ンジWebサイトのコンテキストでは、これは、型破りで、非標準で、無意味で、少なくとも gets(stdin)とは異なる安全ではないプラクティスを、他のプラットフォームです。

私はこれを使用std::cinしてプログラムに入力をストリーミングするのはこれほど難しいことではないと確信しています。


また、ブラインドの「検索と置換」でもありません。それは、場合によっては置換されない場合があるためです。
bolov

4
@bolovは、gets(stdin)それが置き換えられた最初の発生である可能性がありますか?言語の構文や文法を知らないように見えるという意味で、「盲目」を意味しました。
igelを

はい、あなたは正しいです。それは最初の発生を置き換えます。私はメインの前にそれを置いてみました、そしてそれは私が本当に得たものです。
bolov

1
さらなる研究は、そのサイトがC ++だけでなく、すべての言語でそれを行うことを示唆しています-python / ruby​​は、関数呼び出し( "raw_input()"または "STDIN.gets")を使用します。代わりに、その文字列の文字列置換。getline関数の正規表現一致を見つけるのは難しすぎたので、C / C ++のget(stdin)を使用しました。
Stobor

4
@ストーボルダン、あなたは正しい。これはJavaでも発生することを確認できます。行が未定義の場合でもSystem.out.print(FirstFactorial(s.nextLine()9));印刷されます。89s
igelを
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.