Que se passe-t-il avec «gets (stdin)» sur le site coderbyte?

144

Coderbyte est un site de défi de codage en ligne (je l'ai trouvé il y a seulement 2 minutes).

Le premier défi C ++ qui vous est accueilli a un squelette C ++ que vous devez modifier:

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

}

Si vous êtes peu familier avec C ++, la première chose * qui vous vient aux yeux est:

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

Donc, ok, le code appelle getsqui est obsolète depuis C ++ 11 et supprimé depuis C ++ 14 ce qui est mauvais en soi.

Mais alors je me rends compte: getsest de type char*(char*). Donc, il ne devrait pas accepter de FILE*paramètre et le résultat ne devrait pas être utilisable à la place d'un intparamètre, mais ... non seulement il se compile sans aucun avertissement ni erreur, mais il s'exécute et transmet en fait la valeur d'entrée correcte à FirstFactorial.

En dehors de ce site particulier, le code ne se compile pas (comme prévu), alors que se passe-t-il ici?


* En fait, le premier est, using namespace stdmais ce n'est pas pertinent pour mon problème ici.

bolov
la source
Notez que stdindans la bibliothèque standard se trouve un FILE*, et un pointeur vers n'importe quel type est converti en char*, qui est le type de l'argument de gets(). Cependant, vous ne devriez jamais, jamais, jamais écrire ce genre de code en dehors d'un concours C obscurci. Si votre compilateur l'accepte même, ajoutez plus d'indicateurs d'avertissement, et si vous essayez de corriger une base de code contenant cette construction, transformez les avertissements en erreurs.
Davislor
1
@Davislor non ce n'est pas "fonction candidate non viable: aucune conversion connue de 'struct _IO_FILE *' en 'char *' pour le 1er argument"
bolov
3
@Davislor hein, c'est peut-être vrai pour l'ancien C, mais certainement pas pour le C ++.
Quentin
@Quentin Ouais. Cela ne devrait pas se compiler. Le défi prévu était peut-être: «Prenez ce code cassé, lisez dans mes pensées ce qu'il est censé faire et corrigez-le», mais dans ce cas, il devrait y avoir une véritable spécification. Avec des cas de test.
Davislor
6
Je suis surpris que personne n'ait essayé cela, mais gets(stdin )(avec un espace supplémentaire) produit l'erreur C ++ attendue.
Roman Odaisky

Réponses:

174

Je suis le fondateur de Coderbyte et aussi le gars qui a créé ce gets(stdin)hack.

Les commentaires sur cet article indiquent qu'il s'agit d'une forme de recherche et de remplacement, alors laissez-moi vous expliquer pourquoi j'ai fait cela très rapidement.

À l'époque où j'ai créé le site pour la première fois (vers 2012), il ne supportait que JavaScript. Il n'y avait aucun moyen de "lire en entrée" en JavaScript fonctionnant dans le navigateur, il y aurait donc une fonction foo(input)et j'ai utilisé la readline()fonction de Node.js pour l'appeler comme foo(readline()). Sauf que j'étais un enfant et que je ne savais pas mieux, alors j'ai littéralement remplacé readline()par l'entrée au moment de l'exécution. Ainsi foo(readline())est devenu foo(2)ou foo("hello")qui a bien fonctionné pour JavaScript.

Vers 2013/2014, j'ai ajouté plus de langues et utilisé un service tiers pour évaluer le code en ligne, mais il était très difficile de faire stdin / stdout avec les services que j'utilisais, donc je suis resté avec le même idiot find-and-replace pour les langues comme Python, Ruby et éventuellement C ++, C #, etc.

Avance rapide jusqu'à aujourd'hui, j'exécute le code dans mes propres conteneurs, mais je n'ai jamais mis à jour le fonctionnement de stdin / stdout parce que les gens se sont habitués au piratage étrange (certaines personnes ont même posté sur des forums pour expliquer comment le contourner).

Je sais que ce n'est pas la meilleure pratique et qu'il n'est pas utile pour quelqu'un qui apprend une nouvelle langue de voir des hacks comme celui-ci, mais l'idée était que les nouveaux programmeurs ne se soucient pas du tout de la lecture des entrées et se concentrent simplement sur l'écriture de l'algorithme pour résoudre le problème. problème. Une plainte courante concernant le codage des sites de défi il y a des années était que les nouveaux programmeurs passaient beaucoup de temps à trouver comment lire stdinou lire les lignes d'un fichier, je voulais donc que de nouveaux codeurs évitent ce problème sur Coderbyte.

Je mettrai bientôt à jour la page entière de l'éditeur avec le code par défaut et la stdinlecture des langues. Espérons que les programmeurs C ++ apprécieront davantage l'utilisation de Coderbyte :)

Daniel Borowski
la source
20
«[Mais l'idée était que les nouveaux programmeurs ne se soucient pas du tout de la lecture des entrées et se concentrent simplement sur l'écriture de l'algorithme pour résoudre le problème» - et cela ne vous est pas venu à l'esprit, au lieu d'écrire quelque chose qui ressemble à «réel "code, mettez simplement un nom de fonction inventé ou un espace réservé évident à cet endroit? Vraiment curieux.
Ruther Rendommeleigh
25
Je ne m'attendais vraiment pas à choisir une réponse autre que la mienne lorsque j'ai publié ceci. Merci de me prouver le contraire d'une si belle manière. C'est vraiment un plaisir de voir votre réponse.
bolov
4
Très intéressant! Je recommanderais, si vous souhaitez conserver ce hack, de remplacer l'appel de fonction par quelque chose comme TAKE_INPUT, puis d'utiliser votre recherche-remplacement pour l'insérer #define TAKE_INPUT whatever_hereen haut.
Draconis
18
Nous avons besoin de plus de réponses commençant par "Je suis le fondateur de x et aussi celui qui a créé ça" .
pipe
2
@iheanyi Personne n'a demandé que ce soit parfait. En fait, je suis convaincu que presque n'importe quel espace réservé aurait été meilleur que quelque chose qui ressemble à du code valide pour n'importe quel débutant mais qui ne compile pas réellement.
Ruther Rendommeleigh
112

Je suis intrigué. Donc, il est temps de mettre les lunettes d'enquête et comme je n'ai pas accès au compilateur ou aux indicateurs de compilation, je dois devenir inventif. Aussi parce que rien dans ce code n'a de sens, ce n'est pas une mauvaise idée de remettre en question chaque hypothèse.

Commençons par vérifier le type réel de gets. J'ai un petit truc pour ça:

template <class> struct Name;

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

Et cela semble ... normal:

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

getsest marqué comme obsolète et possède la signature char *(char *). Mais alors comment est la FirstFactorial(gets(stdin));compilation?

Essayons autre chose:

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

Ce qui nous donne:

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

Enfin , nous obtenons quelque chose: decltype(8). Donc, le tout a gets(stdin)été remplacé textuellement par l'entrée ( 8).

Et les choses deviennent plus étranges. L'erreur du compilateur continue:

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

Alors maintenant, nous obtenons l'erreur attendue pour cout << FirstFactorial(gets(stdin));

J'ai vérifié une macro et depuis #undef getssemble ne rien faire, il semble que ce ne soit pas une macro.

Mais

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

Il compile.

Mais

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

Pas avec l'erreur attendue à la n2ligne.

Et encore une fois, presque toutes les modifications apportées à mainla ligne cout << FirstFactorial(gets(stdin));crachent l'erreur attendue.

De plus, le stdinsemble être vide.

Je ne peux donc que conclure et spéculer qu'ils ont un petit programme qui analyse la source et essaie (mal) de remplacer gets(stdin)par la valeur d'entrée du cas de test avant de l'introduire dans le compilateur. Si quelqu'un a une meilleure théorie ou sait ce qu'il fait, partagez-le!

C'est évidemment une très mauvaise pratique. En recherchant cela, j'ai trouvé qu'il y avait au moins une question ici ( exemple ) à ce sujet et parce que les gens n'ont aucune idée qu'il existe un site qui le fait, leur réponse est "n'utilisez pas, getsutilisez ... à la place", ce qui est en effet un bon conseil mais ne fait que confondre davantage l'OP puisque toute tentative de lecture valide depuis stdin échouera sur ce site.


TLDR

gets(stdin)n'est pas valide en C ++. C'est un gadget que ce site utilise en particulier (pour quelles raisons je ne peux pas comprendre). Si vous souhaitez continuer à soumettre sur le site (je ne l'approuve ni ne l'approuve), vous devez utiliser cette construction qui autrement n'aurait pas de sens, mais sachez qu'elle est fragile. Presque toutes les modifications apportées à mainvont cracher une erreur. En dehors de ce site, utilisez des méthodes de lecture d'entrée normales.

bolov
la source
27
Je suis vraiment étonné. Peut-être que ce Q / A peut être un article canonique sur pourquoi ne pas apprendre des sites de défis de codage.
alter igel
28
Quelque chose de vraiment mal se passe, et je pense que c'est au niveau du remplacement de texte dans le code source en dehors du compilateur. Essayez ceci: std::cout << "gets(stdin)";et la sortie est 8(ou ce que vous tapez dans le champ 'input'. C'est un abus honteux de la langue.
alter igel
14
@Stobor note les citations autour "gets(stdin)". C'est une chaîne littérale que même le préprocesseur ne toucherait pas
alter igel
2
Pour citer James Kirk: "C'est sacrément étrange."
ApproachingDarknessFish
2
@alterigel descends de ton grand cheval. Cela ne dit pas si l'apprentissage des sites de défis de codage est utile ou non. Qui êtes-vous pour décider comment les gens pratiquent les choses?
Matsemann
66

J'ai essayé l'ajout suivant maindans l'éditeur Coderbyte:

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

Où l'extrait mystérieux et énigmatique gets(stdin)apparaît dans une chaîne littérale. Cela ne devrait pas être transformé par quoi que ce soit, même pas le préprocesseur, et tout programmeur C ++ devrait s'attendre à ce que ce code imprime la chaîne exacte gets(stdin)sur la sortie standard. Et pourtant, nous voyons la sortie suivante, une fois compilée et exécutée sur coderbyte:

8

Où la valeur 8est tirée directement du champ «d'entrée» pratique sous l'éditeur.

Code magique

À partir de là, il est clair que cet éditeur en ligne effectue des opérations de recherche et de remplacement aveugles sur le code source, des apparences de substitution gets(stdin)avec l '«entrée» de l'utilisateur. Personnellement, j'appellerais cela une mauvaise utilisation du langage qui est pire que des macros de préprocesseur imprudentes.

Dans le contexte d'un site Web de défi de codage en ligne, cela m'inquiète car il enseigne des pratiques non conventionnelles, non standard, dénuées de sens et au moins dangereuses comme gets(stdin), et d'une manière qui ne peut pas être répétée sur d'autres plates-formes.

Je suis sûr que ce ne peut pas être ce difficile à utiliser simplement std::cinet juste entrée flux à un programme.

alter igel
la source
et ce n'est même pas un "find and replace" aveugle parce que parfois il le remplace parfois non.
bolov
4
@bolov pourrait-il être juste la première occurrence de ce gets(stdin)qui est remplacé? Je voulais dire «aveugle» dans le sens où il semble ignorer la syntaxe ou la grammaire de la langue.
alter igel
Oui, tu as raison. Il remplace la première occurrence. J'ai essayé de mettre un avant principal et c'est ce que j'ai en effet.
bolov
1
Des recherches plus poussées suggèrent que ce site le fait pour tous les langages, pas seulement C ++ - python / ruby, il utilise l'appel de fonction ("raw_input ()" ou "STDIN.gets") qui renverrait généralement une chaîne de stdin, mais finit par le faire une substitution de chaîne de cette chaîne à la place. Je suppose que trouver une correspondance regex pour la fonction getline était trop difficile, ils sont donc allés avec gets (stdin) pour C / C ++.
Stobor
4
@Stobor dang, vous avez raison. Je peux confirmer que cela se produit également pour Java, la ligne System.out.print(FirstFactorial(s.nextLine()9));s'imprime 89même si elle sn'est pas définie.
alter igel