Was ist los mit 'bekommt (stdin)' auf der Seite coderbyte?

144

Coderbyte ist eine Online-Codierungs-Challenge-Site (ich habe sie erst vor 2 Minuten gefunden).

Die erste C ++ - Herausforderung, mit der Sie begrüßt werden, enthält ein C ++ - Skelett, das Sie ändern müssen:

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

}

Wenn Sie wenig vertraut mit C ++ dem ersten , was sind * , das erscheint in Ihren Augen ist:

int FirstFactorial(int num);
cout << FirstFactorial(gets(stdin));

Also, ok, der Code ruft auf, getsder seit C ++ 11 veraltet und seit C ++ 14 entfernt ist, was an sich schlecht ist.

Aber dann merke ich: getsist vom Typ char*(char*). Es sollte also keinen FILE*Parameter akzeptieren und das Ergebnis sollte nicht anstelle eines intParameters verwendet werden können, sondern ... es wird nicht nur ohne Warnungen oder Fehler kompiliert, sondern es wird ausgeführt und übergibt tatsächlich den richtigen Eingabewert an FirstFactorial.

Außerhalb dieser speziellen Site wird der Code nicht (wie erwartet) kompiliert. Was ist hier also los?


* Eigentlich ist der erste, using namespace stdaber das ist für mein Problem hier irrelevant.

Bolov
quelle
Beachten Sie, dass stdinin der Standardbibliothek ein FILE*und ein Zeiger auf einen beliebigen Typ konvertiert wird char*, der der Typ des Arguments von ist gets(). Sie sollten diese Art von Code jedoch niemals außerhalb eines verschleierten C-Wettbewerbs schreiben. Wenn Ihr Compiler dies überhaupt akzeptiert, fügen Sie weitere Warnflags hinzu. Wenn Sie versuchen, eine Codebasis zu reparieren, in der dieses Konstrukt enthalten ist, verwandeln Sie Warnungen in Fehler.
Davislor
1
@Davislor nein, es ist nicht "Kandidatenfunktion nicht realisierbar: Keine bekannte Konvertierung von 'struct _IO_FILE *' nach 'char *' für das erste Argument"
bolov
3
@Davislor huh, das mag für altes C zutreffen, aber definitiv nicht für C ++.
Quentin
@ Quentin Ja. Das sollte nicht kompiliert werden. Die beabsichtigte Herausforderung könnte gewesen sein: "Nehmen Sie diesen kaputten Code, lesen Sie meine Gedanken darüber, was er tun soll, und beheben Sie ihn", aber in diesem Fall sollte es eine echte Spezifikation geben. Mit Testfällen.
Davislor
6
Ich bin überrascht, dass niemand dies versucht hat, aber gets(stdin )(mit einem zusätzlichen Leerzeichen) den erwarteten C ++ - Fehler erzeugt.
Roman Odaisky

Antworten:

174

Ich bin der Gründer von Coderbyte und auch der Typ, der diesen gets(stdin)Hack erstellt hat.

Die Kommentare zu diesem Beitrag sind richtig, dass es sich um eine Form des Findens und Ersetzens handelt. Lassen Sie mich daher erklären, warum ich dies sehr schnell getan habe.

Damals, als ich die Site zum ersten Mal erstellte (um 2012), unterstützte sie nur JavaScript. Es gab keine Möglichkeit, Eingaben in JavaScript, das im Browser ausgeführt wird, "einzulesen", und daher würde es eine Funktion gebenfoo(input) und ich habe die readline()Funktion von Node.js verwendet, um sie wie folgt aufzurufen foo(readline()). Außer ich war ein Kind und wusste es nicht besser, also habe ich es readline()zur Laufzeit buchstäblich nur durch die Eingabe ersetzt . So foo(readline())wurde foo(2)oder foo("hello")was für JavaScript gut funktionierte.

Um 2013/2014 habe ich weitere Sprachen hinzugefügt und Dienste von Drittanbietern verwendet, um Code online auszuwerten. Es war jedoch sehr schwierig, mit den von mir verwendeten Diensten stdin / stdout durchzuführen. Daher habe ich mich an das gleiche dumme Suchen und Ersetzen für Sprachen gehalten wie Python, Ruby und schließlich C ++, C # usw.

Schneller Vorlauf bis heute, ich führe den Code in meinen eigenen Containern aus, habe aber nie die Funktionsweise von stdin / stdout aktualisiert, weil sich die Leute an den seltsamen Hack gewöhnt haben (einige Leute haben sogar in Foren gepostet, in denen erklärt wird, wie man ihn umgeht).

Ich weiß, dass es keine bewährte Methode ist und es für jemanden, der eine neue Sprache lernt, nicht hilfreich ist, solche Hacks zu sehen, aber die Idee war, dass neue Programmierer sich überhaupt keine Gedanken über das Lesen von Eingaben machen und sich nur darauf konzentrieren, den Algorithmus zu schreiben, um das Problem zu lösen Problem. Eine häufige Beschwerde über Coding-Challenge-Sites vor Jahren war, dass neue Programmierer viel Zeit damit verbringen würden, nur herauszufinden, wie stdinman aus einer Datei liest oder Zeilen aus einer Datei liest. Deshalb wollte ich, dass neue Codierer dieses Problem auf Coderbyte vermeiden.

Ich werde in Kürze die gesamte Editorseite zusammen mit dem Standardcode aktualisieren und stdinfür Sprachen lesen. Hoffentlich werden dann C ++ - Programmierer Coderbyte mehr genießen :)

Daniel Borowski
quelle
20
"[B] aber die Idee war, dass neue Programmierer sich überhaupt nicht um das Lesen von Eingaben kümmern und sich nur darauf konzentrieren, den Algorithmus zu schreiben, um das Problem zu lösen" - und es ist Ihnen nicht in den Sinn gekommen, anstatt etwas zu schreiben, das "echt" ähnelt "Code, setzen Sie einfach einen erfundenen Funktionsnamen oder einen offensichtlichen Platzhalter an diese Stelle? Wirklich neugierig.
Ruther Rendommeleigh
25
Ich hatte wirklich nicht erwartet, dass ich eine andere Antwort als meine eigene wählen würde, als ich dies veröffentlichte. Vielen Dank, dass Sie mir auf so großartige Weise das Gegenteil bewiesen haben. Es ist wirklich eine Freude, Ihre Antwort zu sehen.
Bolov
4
Sehr interessant! Ich würde empfehlen, wenn Sie diesen Hack beibehalten möchten, dass Sie den Funktionsaufruf durch etwas wie TAKE_INPUTersetzen und dann Ihren Find-Replace verwenden, um ihn #define TAKE_INPUT whatever_hereoben einzufügen .
Draconis
18
Wir brauchen mehr Antworten, beginnend mit "Ich bin der Gründer von x und auch der Typ, der das geschaffen hat" .
Pipe
2
@iheanyi Niemand hat darum gebeten, dass es perfekt ist. Tatsächlich bin ich davon überzeugt, dass fast jeder Platzhalter besser gewesen wäre als etwas, das für jeden Neuling wie gültiger Code aussieht , aber nicht wirklich kompiliert wird.
Ruther Rendommeleigh
112

Ich bin fasziniert. Also, Zeit, die Untersuchungsbrille aufzusetzen, und da ich keinen Zugriff auf den Compiler oder die Kompilierungsflags habe, muss ich erfinderisch werden. Auch weil nichts an diesem Code Sinn macht, ist es keine schlechte Idee, jede Annahme in Frage zu stellen.

Lassen Sie uns zuerst den tatsächlichen Typ von überprüfen gets. Ich habe einen kleinen Trick dafür:

template <class> struct Name;

int main() { 
    
    Name<decltype(gets)> n;
  
  // keep this function call here
  cout << FirstFactorial(gets(stdin));
  return 0;
    
}

Und das sieht ... normal aus:

/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.

getsist als veraltet markiert und hat die Unterschrift char *(char *). Aber wie ist es dann?FirstFactorial(gets(stdin)); Kompilieren?

Versuchen wir etwas anderes:

int main() { 
  Name<decltype(gets(stdin))> n;
  
  // keep this function call here
  cout << FirstFactorial(gets(stdin));
  return 0;
    
} 

Was uns gibt:

/tmp/286775780/Main.cpp:15:21: error: implicit instantiation of undefined template 'Name<int>'
  Name<decltype(8)> n;
                    ^

Endlich bekommen wir etwas: decltype(8) . Also wurde das Ganze gets(stdin)textuell durch die Eingabe ersetzt (8 ) ersetzt.

Und die Dinge werden seltsamer. Der Compilerfehler wird fortgesetzt:

/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__;

Jetzt erhalten wir den erwarteten Fehler für cout << FirstFactorial(gets(stdin));

Ich habe nach einem Makro gesucht und seitdem #undef gets es nichts zu tun scheint, sieht es so aus, als wäre es kein Makro.

Aber

std::integral_constant<int, gets(stdin)> n;

Es kompiliert.

Aber

std::integral_constant<int, gets(stdin)> n;    // OK
std::integral_constant<int, gets(stdin)> n2;   // ERROR                                          wtf??

Geht nicht mit dem erwarteten Fehler am n2 Leitung.

Und wieder fast jede Modifikation mainmacht die Liniecout << FirstFactorial(gets(stdin)); den erwarteten Fehler ausspucken zu lassen.

Darüber hinaus die stdin scheint eigentlich leer zu sein.

Ich kann also nur schließen und spekulieren, dass sie ein kleines Programm haben, das die Quelle analysiert und (schlecht) versucht, sie zu ersetzen gets(stdin) den Testfall-Eingabewert bevor sie tatsächlich in den Compiler eingespeist werden. Wenn jemand eine bessere Theorie hat oder tatsächlich weiß, was er tut, teilen Sie dies bitte mit!

Dies ist offensichtlich eine sehr schlechte Praxis. Während ich dies recherchierte, stellte ich fest, dass es hier zumindest eine Frage gibt ( Beispiel ), und da die Leute keine Ahnung haben, dass es da draußen eine Site gibt, die dies tut, lautet ihre Antwort "benutze nicht getsbenutze ... stattdessen", was in der Tat der Fall ist Ein guter Rat, der das OP jedoch nur mehr verwirrt, da jeder Versuch eines gültigen Lesens von stdin auf dieser Site fehlschlägt.


TLDR

gets(stdin)ist ungültig C ++. Es ist eine Spielerei, die diese spezielle Site verwendet (aus welchen Gründen ich es nicht herausfinden kann). Wenn Sie weiterhin auf der Website einreichen möchten (ich unterstütze es weder noch unterstütze ich es), müssen Sie dieses Konstrukt verwenden, das sonst keinen Sinn ergibt, aber sich bewusst ist, dass es spröde ist. Fast alle Änderungen an mainwerden einen Fehler ausspucken. Verwenden Sie außerhalb dieser Site normale Lesemethoden für Eingaben.

Bolov
quelle
27
Ich bin wirklich erstaunt. Vielleicht kann diese Frage / Antwort ein kanonischer Beitrag darüber sein, warum man nicht von der Codierung von Herausforderungsseiten lernen sollte.
Alter igel
28
Es passiert etwas wirklich Böses, und ich denke, es befindet sich auf der Ebene der Textersetzung im Quellcode außerhalb des Compilers. Versuchen Sie Folgendes: std::cout << "gets(stdin)";und die Ausgabe ist 8(oder was auch immer Sie in das 'Eingabe'-Feld
eingeben
14
@Stobor beachten Sie die Anführungszeichen herum "gets(stdin)". Das ist ein String-Literal, das selbst der Präprozessor nicht berühren würde
ändern Sie igel am
2
Um James Kirk zu zitieren: "Das ist verdammt eigenartig."
Annäherung
2
@alterigel steig von deinem hohen Pferd. Dies ist keine Aussage darüber, ob das Lernen von Codierungs-Challenge-Sites nützlich ist oder nicht. Wer bist du, um zu entscheiden, wie Leute Sachen üben?
Matsemann
66

Ich habe mainim Coderbyte-Editor folgende Ergänzung ausprobiert:

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

Wo das mysteriöse und rätselhafte Snippet gets(stdin)in einem String-Literal erscheint. Dies sollte möglicherweise von nichts transformiert werden, nicht einmal vom Präprozessor, und jeder C ++ - Programmierer sollte erwarten, dass dieser Code die genaue Zeichenfolge gets(stdin)in die Standardausgabe druckt . Und doch sehen wir die folgende Ausgabe, wenn sie kompiliert und auf Coderbyte ausgeführt wird:

8

Wobei der Wert 8direkt aus dem praktischen Eingabefeld unter dem Editor übernommen wird.

Magischer Code

Daraus ergibt sich, dass dieser Online-Editor blinde Such- und Ersetzungsvorgänge für den Quellcode ausführt, wobei das Erscheinungsbild gets(stdin)durch die 'Eingabe' des Benutzers ersetzt wird. Ich persönlich würde dies einen Missbrauch der Sprache nennen, der schlimmer ist als unachtsame Präprozessor-Makros.

Im Zusammenhang mit einer Online-Codierungs-Challenge-Website mache ich mir darüber Sorgen, weil sie unkonventionelle, nicht standardmäßige, bedeutungslose und zumindest unsichere Praktiken wie gets(stdin)und auf eine Weise lehrt, die auf anderen Plattformen nicht wiederholt werden kann.

Ich bin sicher , es kann nicht sein die schwer , nur zu verwenden std::cinund nur Strom Eingabe in ein Programm.

alter igel
quelle
und es ist nicht einmal ein blindes "Finden und Ersetzen", weil es es manchmal ersetzt, manchmal nicht.
Bolov
4
@bolov könnte es nur das erste Vorkommen sein gets(stdin), das ersetzt wird? Ich meinte "blind" in dem Sinne, dass es die Syntax oder Grammatik der Sprache nicht zu kennen scheint.
Alter igel
ja, du hast recht. Es ersetzt das erste Vorkommen. Ich habe versucht, eine vor die Hauptleitung zu stellen, und das habe ich tatsächlich bekommen.
Bolov
1
Weitere Untersuchungen legen nahe, dass diese Site dies für alle Sprachen tut, nicht nur für C ++ - Python / Ruby. Sie verwendet den Funktionsaufruf ("raw_input ()" oder "STDIN.gets"), der normalerweise eine Zeichenfolge von stdin zurückgibt, dies jedoch letztendlich tut stattdessen eine Zeichenfolgenersetzung dieser Zeichenfolge. Ich denke, es war zu schwierig, eine Regex-Übereinstimmung für die getline-Funktion zu finden, daher haben sie sich für get (stdin) für C / C ++ entschieden.
Stobor
4
@Stobor dang, du hast recht. Ich kann bestätigen, dass dies auch für Java geschieht. Die Zeile wird auch dann System.out.print(FirstFactorial(s.nextLine()9));gedruckt 89, wenn sie nicht sdefiniert ist.
Alter igel