Cosa sta succedendo con 'gets (stdin)' sul sito coderbyte?


144

Coderbyte è un sito di sfide di codifica online (l'ho trovato solo 2 minuti fa).

La prima sfida C ++ che ti viene accolta ha uno scheletro C ++ che devi modificare:

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

}

Se hai poca familiarità con il C ++, la prima cosa * che si apre ai tuoi occhi è:

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

Quindi, ok, il codice chiama getsche è deprecato da C ++ 11 e rimosso da C ++ 14 che è di per sé un male.

Ma poi realizzo: getsè di tipo char*(char*). Quindi non dovrebbe accettare un FILE*parametro e il risultato non dovrebbe essere utilizzabile al posto di un intparametro, ma ... non solo viene compilato senza avvertimenti o errori, ma viene eseguito e in realtà passa il valore di input corretto a FirstFactorial.

Al di fuori di questo particolare sito, il codice non viene compilato (come previsto), quindi cosa sta succedendo qui?


* In realtà il primo è using namespace stdma questo è irrilevante per il mio problema qui.


Si noti che stdinnella libreria standard è un FILE*, e un puntatore a qualsiasi tipo viene convertito in char*, che è il tipo dell'argomento di gets(). Tuttavia, non dovresti mai, mai, mai scrivere quel tipo di codice al di fuori di un contest offuscato C. Se il compilatore lo accetta, aggiungi altri flag di avviso e se stai cercando di correggere una base di codice che contiene quel costrutto, trasforma gli avvisi in errori.
Davislor

1
@Davislor no "Funzione candidata non praticabile: nessuna conversione nota da 'struct _IO_FILE *' a 'char *' per il 1 ° argomento"
bolov

3
@Davislor eh, questo potrebbe essere vero per l'antica C, ma sicuramente non per C ++.
Quentin,

@Quentin Sì. Non dovrebbe compilare. La sfida prevista potrebbe essere stata: "Prendi questo codice non funzionante, leggi la mia mente su cosa dovrebbe fare e correggilo", ma in tal caso dovrebbe esserci una specifica specifica. Con casi di test.
Davislor,

6
Sono sorpreso che nessuno ci abbia provato, ma gets(stdin )(con uno spazio extra) produce l'errore C ++ previsto.
Roman Odaisky,

Risposte:


174

Sono il fondatore di Coderbyte e anche il ragazzo che ha creato questo gets(stdin)hack.

I commenti su questo post sono corretti sul fatto che si tratta di una forma di ricerca e sostituzione, quindi lasciami spiegare perché l'ho fatto molto rapidamente.

Ai tempi in cui ho creato il sito per la prima volta (intorno al 2012), supportava solo JavaScript. Non c'era modo di "leggere in input" in JavaScript in esecuzione nel browser, e quindi ci sarebbe stata una funzione foo(input)e ho usato la readline()funzione da Node.js per chiamarla come foo(readline()). Tranne che ero un bambino e non sapevo di meglio, quindi ho letteralmente sostituito readline()con l'input in fase di esecuzione. Così è foo(readline())diventato foo(2)o foo("hello")che ha funzionato bene per JavaScript.

Intorno al 2013/2014 ho aggiunto più lingue e usato un servizio di terze parti per valutare il codice online, ma è stato molto difficile fare stdin / stdout con i servizi che stavo usando, quindi mi sono bloccato con lo stesso stupido cerca e sostituisci per le lingue come Python, Ruby e infine C ++, C #, ecc.

Velocemente fino ad oggi, eseguo il codice nei miei contenitori, ma non ho mai aggiornato il modo in cui stdin / stdout funziona perché le persone si sono abituate allo strano hack (alcune persone hanno persino pubblicato nei forum che spiegano come aggirarlo).

So che non è una buona pratica e non è utile per qualcuno che impara una nuova lingua a vedere hack come questo, ma l'idea era che i nuovi programmatori non si preoccupassero affatto di leggere l'input e si concentrassero solo sulla scrittura dell'algoritmo per risolvere il problema. Una lamentela comune sui siti di sfida della codifica anni fa era che i nuovi programmatori avrebbero trascorso molto tempo a capire come leggere stdino leggere le righe di un file, quindi volevo che i nuovi programmatori evitassero questo problema su Coderbyte.

Presto aggiornerò l'intera pagina dell'editor insieme al codice predefinito e stdinleggerò per le lingue. Speriamo che i programmatori C ++ apprezzino di più l'uso di Coderbyte :)


20
"[B] ut l'idea era che i nuovi programmatori non si preoccupassero affatto di leggere l'input e si concentrassero solo sulla scrittura dell'algoritmo per risolvere il problema" - e non ti è venuto in mente di farlo, invece di scrivere qualcosa che assomiglia a "reale "codice, basta inserire un nome di funzione inventato o un segnaposto ovvio in quel punto? Davvero curioso.
Ruther Rendommeleigh,

25
Sinceramente non mi aspettavo che avrei scelto una risposta diversa dalla mia quando l'ho pubblicata. Grazie per avermi dimostrato che mi sbaglio in modo fantastico. È davvero un piacere vedere la tua risposta.
Bolov,

4
Molto interessante! Vorrei raccomandare, se si desidera mantenere questo hack, di sostituire la chiamata di funzione con qualcosa di simile TAKE_INPUT, quindi utilizzare la ricerca-sostituzione per inserire #define TAKE_INPUT whatever_herenella parte superiore.
Draconis,

18
Abbiamo bisogno di più risposte a partire da "Sono il fondatore di x e anche il ragazzo che ha creato questo" .
pipe

2
@iheanyi Nessuno ha chiesto che fosse perfetto. In effetti, sono convinto che quasi ogni segnaposto sarebbe stato meglio di qualcosa che assomiglia a un codice valido per qualsiasi principiante ma che in realtà non viene compilato.
Ruther Rendommeleigh il

112

Sono incuriosito. Quindi, è tempo di indossare gli occhiali di indagine e dato che non ho accesso al compilatore o ai flag di compilazione, devo essere inventivo. Anche perché nulla di questo codice ha senso non è una cattiva idea in ogni ipotesi.

Per prima cosa controlliamo il tipo effettivo di gets. Ho un piccolo trucco per questo:

template <class> struct Name;

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

E questo sembra ... normale:

/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è contrassegnato come obsoleto e ha la firma char *(char *). Ma allora com'èFirstFactorial(gets(stdin)); compilando?

Proviamo qualcos'altro:

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

Che ci dà:

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

Finalmente stiamo ottenendo qualcosa: decltype(8). Quindi l'intero è gets(stdin)stato sostituito testualmente con l'input (8 ).

E le cose diventano più strane. L'errore del compilatore continua:

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

Quindi ora otteniamo l'errore previsto per cout << FirstFactorial(gets(stdin));

Ho controllato per una macro e da allora #undef gets sembra non fare nulla sembra che non sia una macro.

Ma

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

Si compila.

Ma

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

Non con l'errore previsto al n2 riga.

E ancora, quasi ogni modifica per mainrendere la lineacout << FirstFactorial(gets(stdin)); sputare l'errore previsto.

Inoltre il stdin realtà sembra essere vuoto.

Quindi posso solo concludere e ipotizzare che abbiano un piccolo programma che analizza la fonte e cerca (male) di sostituire gets(stdin) il valore di input del test case prima di inserirlo nel compilatore. Se qualcuno ha una teoria migliore o effettivamente sa cosa sta facendo, per favore condividi!

Questa è ovviamente una pessima pratica. Durante la ricerca di questo ho scoperto che c'è almeno una domanda qui ( esempio ) su questo e perché le persone non hanno idea che ci sia un sito là fuori che fa questo la loro risposta è "non usare getsuso ... invece" che è davvero un buon consiglio, ma confonde solo di più l'OP poiché qualsiasi tentativo di lettura valida da stdin fallirà su questo sito.


TLDR

gets(stdin)non è C ++ valido. È un espediente che questo particolare sito utilizza (per quali motivi non riesco a capire). Se vuoi continuare a inviare sul sito (non lo sto sostenendo né lo sto sostenendo) devi usare questo costrutto che altrimenti non avrebbe senso, ma tieni presente che è fragile. Quasi tutte le modifiche apportate maingenereranno un errore. Al di fuori di questo sito utilizzare i normali metodi di lettura dell'input.


27
Sono sinceramente stupito. Forse questo Q / A può essere un post canonico sul perché non imparare dai siti di sfida di codifica.
alter igel,

28
Sta succedendo qualcosa di veramente malvagio e penso che sia a livello di sostituzione del testo nel codice sorgente al di fuori del compilatore. Prova questo: std::cout << "gets(stdin)";e l'output è 8(o qualunque cosa digiti nel campo 'input'. Questo è un vergognoso abuso della lingua.
alter igel

14
@Stobor annota le virgolette "gets(stdin)". Questa è una stringa letterale che nemmeno il preprocessore potrebbe toccare
alter igel

2
Per citare James Kirk: "Questo è dannatamente strano."
ApproachingDarknessFish

2
@alterigel scendi dal tuo cavallo alto. Questa non è un'affermazione sull'utilità o meno dell'apprendimento dai siti di sfida di codifica. Chi sei tu per decidere come le persone praticano le cose?
Matsemann,

66

Ho provato la seguente aggiunta a mainnell'editor Coderbyte:

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

Dove il frammento misterioso ed enigmatico gets(stdin)appare all'interno di una stringa letterale. Questo non dovrebbe essere trasformato da nulla, nemmeno dal preprocessore, e qualsiasi programmatore C ++ dovrebbe aspettarsi che questo codice stampi la stringa esatta gets(stdin)sull'output standard. Eppure vediamo il seguente output, quando compilato ed eseguito su coderbyte:

8

Dove il valore 8viene preso direttamente dal comodo campo 'input' sotto l'editor.

Codice magico

Da questo, è chiaro che questo editor online sta eseguendo cieche operazioni di ricerca e sostituzione sul codice sorgente, apparizioni di sostituzione di gets(stdin) con "input" dell'utente. Personalmente chiamerei questo un uso improprio della lingua che è peggio delle macro disattente del preprocessore.

Nel contesto di un sito Web di sfide di codifica online, sono preoccupato per questo perché insegna pratiche non convenzionali, non standard, insignificanti e almeno non sicure comegets(stdin) , e in un modo che non può essere ripetuto su altre piattaforme.

Sono sicuro che non può essere così difficile da usare std::cine trasmettere semplicemente l'input a un programma.


e non è nemmeno un cieco "trova e sostituisci" perché a volte lo sostituisce a volte no.
Bolov,

4
@bolov potrebbe essere solo la prima occorrenza gets(stdin)che viene sostituita? Intendevo "cieco", nel senso che sembra non essere a conoscenza della sintassi o della grammatica della lingua.
alter igel,

si hai ragione. Sostituisce la prima occorrenza. Ho provato a metterne uno prima di quello principale ed è quello che ho ottenuto davvero.
Bolov,

1
Ulteriori ricerche suggeriscono che quel sito lo fa per tutte le lingue, non solo per C ++ - python / ruby ​​usa la funzione call ("raw_input ()" o "STDIN.gets") che di solito restituisce una stringa da stdin, ma finisce per farlo una sostituzione di stringa di quella stringa invece. Immagino che trovare una corrispondenza regex per la funzione getline fosse troppo difficile, quindi sono andati con get (stdin) per C / C ++.
Stobor,

4
@Stobor dang, hai ragione. Posso confermare che ciò accade anche per Java, la linea viene System.out.print(FirstFactorial(s.nextLine()9));stampata 89anche quando snon è definita.
alter igel,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.