Co się dzieje z 'gets (stdin)' w witrynie coderbyte?

144

Coderbyte to internetowa strona z wyzwaniami z kodowania (znalazłem ją zaledwie 2 minuty temu).

Pierwsze wyzwanie C ++, z którym się witasz, zawiera szkielet C ++, który musisz zmodyfikować:

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

}

Jeśli słabo znasz C ++, pierwszą rzeczą, która * pojawia się w twoich oczach, jest:

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

Więc ok, kod wywołuje, getsktóry jest przestarzały od C ++ 11 i usunięty od C ++ 14, który sam w sobie jest zły.

Ale potem zdaję sobie sprawę: getsjest typowy char*(char*). Dlatego nie powinien akceptować FILE*parametru, a wynik nie powinien być użyteczny zamiast intparametru, ale ... nie tylko kompiluje się bez żadnych ostrzeżeń i błędów, ale działa i faktycznie przekazuje poprawną wartość wejściową do FirstFactorial.

Poza tą konkretną witryną kod nie kompiluje się (zgodnie z oczekiwaniami), więc co się tutaj dzieje?


* Właściwie pierwszy z nich jest taki, using namespace stdale nie ma to związku z moim problemem.

bolov
źródło
Zauważ, że stdinw standardowej bibliotece jest a FILE*, a wskaźnik do dowolnego typu jest konwertowany na char*, który jest typem argumentu gets(). Jednak nigdy, przenigdy nie powinieneś pisać tego rodzaju kodu poza zaciemnionym konkursem w C. Jeśli Twój kompilator w ogóle to akceptuje, dodaj więcej flag ostrzegawczych, a jeśli próbujesz naprawić bazę kodu zawierającą tę konstrukcję, zamień ostrzeżenia w błędy.
Davislor,
1
@Davislor nie, nie "Funkcja kandydata nie jest możliwa: brak znanej konwersji z 'struct _IO_FILE *' na 'char *' dla pierwszego argumentu"
bolov
3
@Davislor huh, może to być prawda dla starożytnego C, ale zdecydowanie nie dla C ++.
Quentin,
@Quentin Yeah. To nie powinno się kompilować. Zamierzonym wyzwaniem mogło być: „Weź ten uszkodzony kod, przeczytaj mi w myślach, co ma zrobić, i napraw to”, ale w takim przypadku powinna istnieć prawdziwa specyfikacja. Z przypadkami testowymi.
Davislor,
6
Jestem zaskoczony, że nikt tego nie próbował, ale gets(stdin )(z dodatkową spacją) powoduje oczekiwany błąd C ++.
Roman Odaisky

Odpowiedzi:

174

Jestem założycielem Coderbyte, a także facetem, który stworzył ten gets(stdin)hack.

Komentarze do tego posta są poprawne, że jest to forma znajdź i zamień, więc pozwól mi wyjaśnić, dlaczego zrobiłem to naprawdę szybko.

Kiedy stworzyłem tę witrynę (około 2012), obsługiwała ona tylko JavaScript. Nie było sposobu, aby „wczytać dane wejściowe” w JavaScript działającym w przeglądarce, więc pojawiłaby się funkcja foo(input)i użyłem readline()funkcji z Node.js, aby ją nazwać foo(readline()). Z wyjątkiem tego, że byłem dzieckiem i nie wiedziałem lepiej, więc dosłownie zastąpiłem readline()dane wejściowe w czasie wykonywania. Tak się foo(readline())stało foo(2)lub foo("hello")co działało dobrze dla JavaScript.

Około 2013/2014 dodałem więcej języków i korzystałem z usług innych firm do oceny kodu online, ale bardzo trudno było wykonać stdin / stdout z usługami, z których korzystałem, więc utknąłem z tym samym głupim znajdź-i-zamień dla języków jak Python, Ruby i ostatecznie C ++, C # itp.

Szybko do przodu, uruchamiam kod w moich własnych kontenerach, ale nigdy nie zaktualizowałem sposobu, w jaki działa stdin / stdout, ponieważ ludzie przyzwyczaili się do dziwnego włamania (niektórzy nawet pisali na forach, wyjaśniając, jak to obejść).

Wiem, że to nie jest najlepsza praktyka i nie jest pomocne, aby ktoś uczący się nowego języka widział takie hacki, ale pomysł był taki, aby nowi programiści nie martwili się w ogóle czytaniem danych wejściowych i po prostu skupili się na pisaniu algorytmu, aby rozwiązać problem problem. Jedną z częstych skarg dotyczących witryn z wyzwaniami dla kodowania lata temu było to, że nowi programiści spędzali dużo czasu tylko na zastanawianiu się, jak czytać stdinlub czytać wiersze z pliku, więc chciałem, aby nowi programiści uniknęli tego problemu w Coderbyte.

Niedługo zaktualizuję całą stronę edytora wraz z domyślnym kodem i stdinczytaniem języków. Miejmy nadzieję, że programiści C ++ będą bardziej zadowoleni z używania Coderbyte :)

Daniel Borowski
źródło
20
„[B] Ale pomysł polegał na tym, aby nowi programiści nie martwili się o odczytywanie danych wejściowych i po prostu skupili się na napisaniu algorytmu do rozwiązania problemu” - i nie przyszło ci do głowy, zamiast pisać coś, co przypomina „prawdziwy "kod, po prostu umieść w tym miejscu wymyśloną nazwę funkcji lub oczywisty symbol zastępczy? Naprawdę ciekawy.
Ruther Rendommeleigh
25
Naprawdę nie spodziewałem się, że wybiorę odpowiedź inną niż moja, kiedy to opublikowałem. Dziękuję za udowodnienie mi, że się mylę w tak wspaniały sposób. Naprawdę miło jest zobaczyć twoją odpowiedź.
bolov
4
Bardzo interesujące! Poleciłbym, jeśli chcesz zachować ten hack, aby zastąpić wywołanie funkcji czymś podobnym TAKE_INPUT, a następnie użyć polecenia znajdź-zamian, aby wstawić #define TAKE_INPUT whatever_herena górze.
Draconis
18
Potrzebujemy więcej odpowiedzi, zaczynając od „Jestem założycielem x, a także facetem, który to stworzył” .
fajka
2
@iheanyi Nikt nie prosił, aby był doskonały. W rzeczywistości jestem przekonany, że prawie każdy symbol zastępczy byłby lepszy niż coś, co wygląda jak prawidłowy kod dla każdego początkującego, ale w rzeczywistości się nie kompiluje.
Ruther Rendommeleigh
112

Jestem zaintrygowany. Czas więc założyć gogle śledcze, a ponieważ nie mam dostępu do kompilatora ani flag kompilacji, muszę się wykazać pomysłowością. Ponieważ nic w tym kodzie nie ma sensu, nie jest to zły pomysł, kwestionuj każde założenie.

Najpierw sprawdźmy rzeczywisty typ gets. Mam na to małą sztuczkę:

template <class> struct Name;

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

A to wygląda ... normalnie:

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

getsjest oznaczony jako przestarzały i ma podpis char *(char *). Ale jak wygląda FirstFactorial(gets(stdin));kompilacja?

Spróbujmy czegoś innego:

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

Co daje nam:

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

Wreszcie jesteśmy coraz coś: decltype(8). Więc całość gets(stdin)została zastąpiona tekstowo przez input ( 8).

I rzeczy stają się dziwniejsze. Błąd kompilatora jest kontynuowany:

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

Więc teraz otrzymujemy oczekiwany błąd dla cout << FirstFactorial(gets(stdin));

Sprawdziłem, czy nie ma makra, a ponieważ #undef getswydaje się nic nie robić, wygląda na to, że nie jest to makro.

Ale

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

Kompiluje się.

Ale

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

Nie z oczekiwanym błędem na n2linii.

I znowu, prawie każda modyfikacja mainpowoduje, że linia cout << FirstFactorial(gets(stdin));wypluwa oczekiwany błąd.

Co więcej, stdinfaktycznie wydaje się być pusty.

Mogę więc tylko wywnioskować i spekulować, że mają mały program, który analizuje źródło i próbuje (słabo) zastąpić gets(stdin)go wartością wejściową przypadku testowego, zanim faktycznie wprowadzi go do kompilatora. Jeśli ktoś ma lepszą teorię lub naprawdę wie, co robi, podziel się nim!

To oczywiście bardzo zła praktyka. Podczas badania tego stwierdziłem, że jest tu przynajmniej pytanie ( przykład ) na ten temat, a ponieważ ludzie nie mają pojęcia, że ​​istnieje strona, która to robi, ich odpowiedź brzmi "nie getsużywaj ... zamiast tego", co rzeczywiście jest dobra rada, ale tylko bardziej dezorientuje OP, ponieważ każda próba prawidłowego odczytu ze standardowego wejścia zakończy się niepowodzeniem w tej witrynie.


TLDR

gets(stdin)jest nieprawidłowy C ++. To sztuczka, której używa ta konkretna witryna (z jakich powodów nie mogę się dowiedzieć). Jeśli chcesz nadal publikować na stronie (ani nie popieram ani nie popieram), musisz użyć tej konstrukcji, która w przeciwnym razie nie miałaby sensu, ale pamiętaj, że jest krucha. Prawie każda modyfikacja mainspowoduje wyświetlenie błędu. Poza tą witryną używaj normalnych metod odczytu danych wejściowych.

bolov
źródło
27
Jestem szczerze zdumiony. Może to pytanie / odpowiedzi może być kanonicznym postem na temat tego, dlaczego nie należy uczyć się na stronach z wyzwaniami kodowania.
alter igel
28
Dzieje się coś naprawdę złego i myślę, że dzieje się to na poziomie zastępowania tekstu w kodzie źródłowym poza kompilatorem. Spróbuj tego: std::cout << "gets(stdin)";a wynik to 8(lub cokolwiek wpiszesz w polu „input”. To haniebne nadużycie języka.
alter igel
14
@Stobor zwróć uwagę na cytaty wokół "gets(stdin)". To dosłowny ciąg znaków, którego nawet preprocesor nie tknąłby
zmiana igel
2
Cytując Jamesa Kirka: „To jest cholernie dziwne”.
ApproachingDarknessFish
2
@alterigel zejdź z wysokiego konia. Nie jest to stwierdzenie, czy uczenie się na stronach z wyzwaniami kodowania jest przydatne, czy nie. Kim jesteś, aby decydować, jak ludzie ćwiczą?
Matsemann
66

Próbowałem następującego dodatku do mainw edytorze Coderbyte:

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

Gdzie tajemniczy i enigmatyczny fragment gets(stdin)pojawia się wewnątrz literału ciągu. Nie powinno to być w żaden sposób przekształcane, nawet przez preprocesor, a każdy programista C ++ powinien oczekiwać, że ten kod wypisze dokładny ciąg gets(stdin)na standardowe wyjście. A jednak widzimy następujące dane wyjściowe, gdy są kompilowane i uruchamiane na coderbyte:

8

Gdzie wartość 8jest pobierana bezpośrednio z wygodnego pola „wejście” w edytorze.

Magiczny kod

Z tego jasno wynika, że ​​ten edytor online wykonuje ślepe operacje znajdowania i zamieniania na kodzie źródłowym, zastępując wygląd gets(stdin)przez „dane wejściowe” użytkownika. Osobiście nazwałbym to niewłaściwym użyciem języka, które jest gorsze niż nieostrożne makra preprocesora.

W kontekście strony internetowej z wyzwaniami kodowania online martwię się tym, ponieważ uczy ona niekonwencjonalnych, niestandardowych, bezsensownych i przynajmniej niebezpiecznych praktyk, takich jak gets(stdin)iw sposób, którego nie można powtórzyć na innych platformach.

Jestem pewien, że nie może być tak trudno po prostu używać std::cini przesyłać strumieniowo danych wejściowych do programu.

alter igel
źródło
i nie jest to nawet ślepa funkcja „znajdź i zamień”, ponieważ czasami zastępuje ją, a czasami nie.
bolov
4
@bolov czy to może być tylko pierwsze wystąpienie gets(stdin)tej zamiany? Miałem na myśli „ślepy” w tym sensie, że wydaje się być nieświadomy składni lub gramatyki języka.
alter igel
tak masz rację. Zastępuje pierwsze wystąpienie. Próbowałem postawić jeden przed głównym i to właśnie dostałem.
bolov
1
Dalsze badania sugerują, że ta strona robi to dla wszystkich języków, nie tylko C ++ - python / ruby, używa wywołania funkcji ("raw_input ()" lub "STDIN.gets"), które zwykle zwracałyby ciąg ze standardowego wejścia, ale kończy się na tym zamiast tego zastąpienie tego ciągu. Wydaje mi się, że znalezienie dopasowania wyrażenia regularnego dla funkcji getline było zbyt trudne, więc zdecydowali się na pobranie (stdin) dla C / C ++.
Stobor
4
@Stobor dang, masz rację. Mogę potwierdzić, że dzieje się tak również w przypadku Javy, linia jest System.out.print(FirstFactorial(s.nextLine()9));drukowana, 89nawet jeśli sjest niezdefiniowana.
alter igel