Co się dzieje z "gets (stdin)" na stronie coderbyte?

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

Pierwsze wyzwanie C++ , którym jesteś witany, ma 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 mało znasz C++ pierwszą rzeczą* to wyskakuje Ci w oczach:

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

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

Ale wtedy zdaję sobie sprawę,: gets jest typu char*(char*). Nie powinien więc przyjmować parametru FILE*, a wynik nie powinien być użyteczny zamiast parametru int, ale ... nie tylko kompiluje bez żadnych ostrzeżeń i błędów, ale uruchamia się i przekazuje poprawną wartość wejściową do FirstFactorial.

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


*właściwie pierwszy jest using namespace std, ale to nie ma znaczenia dla mojego problemu tutaj.

Author: Peter Mortensen, 2019-03-20

3 answers

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

Komentarze do tego postu są poprawne, że jest to forma find-and-replace, więc pozwól mi wyjaśnić, dlaczego zrobiłem to naprawdę szybko.

W czasach, kiedy po raz pierwszy stworzyłem tę stronę (Około 2012), obsługiwała ona tylko JavaScript. Nie było sposobu na "odczyt input" w JavaScript uruchomiony w przeglądarce, a więc nie byłoby funkcji foo(input) i użyłem funkcji readline() z węzła.js żeby to nazwać jak foo(readline()). Poza tym, że byłem dzieckiem i nie wiedziałem lepiej, więc dosłownie zamieniłem readline() z wejściem w czasie wykonywania. Tak więc foo(readline()) stało się foo(2) lub foo("hello"), które działały dobrze dla JavaScript.

Około roku 2013/2014 dodałem więcej języków i użyłem usługi innej firmy do oceny kodu online, ale bardzo trudno było zrobić stdin/stdout z usługami, których używałem, więc utknąłem z tym samym głupim find-and-replace dla języków takich jak Python, Ruby, a ostatecznie C++, C# itp.

Fast forward do dziś uruchamiam kod w moich własnych kontenerach, ale nigdy nie aktualizowałem sposobu działania stdin/stdout, ponieważ ludzie przyzwyczaili się do dziwnego hack ' a (niektórzy nawet napisali na forach wyjaśniających, jak go obejść).

Wiem, że nie jest to najlepsza praktyka i nie jest pomocne dla kogoś uczącego się nowego języka, aby zobaczyć takie hacki, ale pomysł był taki, aby nowi programiści nie martwili się czytaniem danych wejściowych i po prostu skupili się na pisaniu algorytmu, aby rozwiązać problem. Jeden wspólny wiele lat temu narzekano na to, że nowi Programiści spędzali dużo czasu tylko zastanawiając się, jak czytać z stdin lub czytać wiersze z pliku, więc chciałem nowych programistów, aby uniknąć tego problemu na Coderbyte.

Wkrótce zaktualizuję całą stronę edytora wraz z domyślnym kodem i stdin czytaniem języków. Mam nadzieję, że programiści C++ będą bardziej zadowoleni z używania Coderbyte:) [11]}

 173
Author: Daniel Borowski,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-03-27 21:57:41

Jestem zaintrygowany. Czas więc założyć gogle śledzące, a ponieważ nie mam dostępu do flag kompilatora lub kompilacji, muszę zacząć wymyślać. Również dlatego, że nic w tym kodzie nie ma sensu, to nie jest zły pomysł pytanie 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;
    
}

I to wygląda ... normalny:

/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 jest oznaczony jako przestarzały i posiada podpis char *(char *). Ale jak to jest FirstFactorial(gets(stdin)); kompilowanie?

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 coś dostajemy: decltype(8). Tak więc całość {[12] } została tekstowo zastąpiona przez input (8).

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

/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 gets wydaje się, że nic nie robi, wygląda na to, że nie jest to makro.

Ale

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

Kompiluje.

Ale

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

Nie ma oczekiwanego błędu w linii n2.

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

Ponadto stdin faktycznie wydaje się być pusty.

Więc mogę tylko wnioskować i spekulować, że mają mały program, który parsuje źródło i próbuje (słabo) zastąpić gets(stdin) z wartością wejściową przypadku testowego przed faktycznym karmieniem do kompilatora. Jeśli ktoś ma lepszą teorię lub faktycznie wie, co robi, Proszę się podzielić!

To oczywiście bardzo zła praktyka. Podczas badania tego znalazłem tu przynajmniej pytanie ( przykład) o tym, a ponieważ ludzie nie mają pojęcia, że istnieje strona, która to robi, ich odpowiedź brzmi " nie używaj gets użyj ... zamiast" co jest rzeczywiście dobrą radą, ale tylko myli OP bardziej, ponieważ każda próba poprawnego odczytu z stdin zawiedzie na ta strona.

TLDR

gets(stdin) jest nieprawidłowym C++. To sztuczka, której używa ta konkretna strona (z jakich powodów nie mogę się domyślić). Jeśli chcesz nadal składać 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 wszelkie modyfikacje main wypluwają błąd. Poza tą stroną stosuj normalne metody odczytu danych wejściowych.

 112
Author: bolov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-06-20 09:12:55

Wypróbowałem następujący dodatek do main w edytorze Coderbyte:

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

Gdzie tajemniczy i enigmatyczny fragment gets(stdin) pojawia się wewnątrz literału łańcuchowego. To nie powinno być przekształcane przez cokolwiek, nawet preprocesor, i każdy Programista C++ powinien oczekiwać, że ten kod wydrukuje dokładny łańcuch gets(stdin) na standardowym wyjściu. Po skompilowaniu i uruchomieniu na coderbyte widzimy następujące wyjście:

8

Gdzie wartość 8 jest wzięta prosto z wygodnego pola "input" pod edytorem.

Magiczny kod

Z tego wynika, że Edytor online wykonuje ślepe operacje znajdowania i zastępowania kodu źródłowego, zastępując wygląd gets(stdin) "wejściem" użytkownika. Osobiście nazwałbym to nadużyciem języka, który jest gorszy niż nieostrożne makra preprocesora.

W kontekście strony internetowej online coding challenge, martwię się tym, ponieważ uczy niekonwencjonalnych, niestandardowe, bezsensowne i co najmniej niebezpieczne praktyki takie jak gets(stdin) i w sposób, którego nie można powtórzyć na innych platformach.

Jestem pewien, że to nie może być to trudne do użycia std::cin i po prostu strumienia danych wejściowych do programu.

 66
Author: alter igel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-03-22 13:54:31