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++ vous êtes accueilli avec 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 saute aux yeux c'est:
int FirstFactorial(int num);
cout << FirstFactorial(gets(stdin));
Donc, ok, le code appelle gets
qui est obsolète depuis C++ 11 et supprimé depuis C++ 14 qui est mauvais en soi.
Mais alors je me rends compte: gets
est de type char*(char*)
. Il ne devrait donc pas accepter un paramètre FILE*
Et le résultat ne devrait pas être utilisable à la place d'un paramètre int
, mais ... non seulement il compile sans aucun avertissement ni erreur, mais il s'exécute et transmet 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 std
Mais cela n'a rien à voir avec mon problème ici.
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 drapeaux de compilation, j'ai besoin de faire preuve d'inventivité. Aussi parce que rien sur ce code n'a de sens, ce n'est pas une mauvaise idée de remettre en question toutes les hypothèses.
Vérifions d'abord 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 ça a l'air ... 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.
gets
est marqué comme obsolète et porte la signature char *(char *)
. Mais alors comment compile FirstFactorial(gets(stdin));
?
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)
. Ainsi, l'intégralité de gets(stdin)
a été textuellement remplacée 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 puisque #undef gets
Semble 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 sur la ligne n2
.
Et encore une fois, presque toute modification de main
fait que la ligne cout << FirstFactorial(gets(stdin));
crache l'erreur attendue.
De plus, le stdin
semble en fait 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 scénario de test avant de l'introduire dans le compilateur. Si quelqu'un a une meilleure théorie ou sait réellement 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 fait cela, leur réponse est "n'utilisez pas gets
utilisez ... à la place ", ce qui est en effet un bon conseil mais ne fait que confondre davantage l'OP car toute tentative de lecture valide depuis stdin échouera sur ce site.
gets(stdin)
n'est pas C++ non valide. C'est un gadget que ce site utilise (pour quelles raisons je ne peux pas le comprendre). Si vous souhaitez continuer à soumettre sur le site (je ne l'approuve pas ni ne l'approuve pas), vous devez utiliser cette construction qui autrement n'aurait pas de sens, mais sachez qu'elle est fragile. Presque toutes les modifications apportées à main
généreront une erreur. En dehors de ce site, utilisez des méthodes de lecture d'entrée normales.
Je suis le fondateur de Coderbyte et aussi le gars qui a créé ce gets(stdin)
hack.
Les commentaires sur ce post sont corrects, c'est une forme de recherche et 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" dans JavaScript exécuté dans le navigateur, et donc il y aurait une fonction foo(input)
et j'ai utilisé la fonction readline()
de Node.js pour l'appeler comme foo(readline())
. Sauf que j'étais un enfant et que je ne savais pas mieux, j'ai donc littéralement remplacé readline()
par l'entrée au moment de l'exécution. Ainsi, foo(readline())
est devenu foo(2)
ou foo("hello")
, ce qui fonctionnait bien 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 de recherche et de remplacement 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 bizarre (certaines personnes ont même posté dans des forums expliquant 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 lire les entrées et se concentrent uniquement sur l'écriture de l'algorithme pour résoudre le problème. problème. Il y a des années, une plainte fréquente concernant le codage de sites difficiles était que les nouveaux programmeurs passeraient beaucoup de temps à trouver comment lire à partir de stdin
ou lire des lignes à partir d'un fichier, donc je voulais que de nouveaux codeurs évitent ce problème sur Coderbyte. .
Je mettrai bientôt à jour toute la page de l'éditeur avec le code par défaut et la lecture de stdin
pour les langues. Espérons que les programmeurs C++ apprécieront davantage Coderbyte :)
J'ai essayé l'ajout suivant à main
dans l'éditeur Coderbyte:
std::cout << "gets(stdin)";
Où l'extrait mystérieux et énigmatique gets(stdin)
apparaît à l'intérieur d'un littéral de chaîne. Cela ne devrait pas être transformé par quoi que ce soit, pas même le préprocesseur, et n'importe quel programmeur C++ devrait s'attendre à ce que ce code imprime la chaîne exacte gets(stdin)
à la sortie standard. Et pourtant, nous voyons la sortie suivante, une fois compilée et exécutée sur coderbyte:
8
Où la valeur 8
Est tirée directement du champ de saisie pratique sous l'éditeur.
De cela, il est clair que cet éditeur en ligne effectue des opérations aveugles de recherche et de remplacement sur le code source, des apparences de substitution de gets(stdin)
avec la 'saisie' de l'utilisateur. Personnellement, j'appellerais cela une mauvaise utilisation du langage pire que les macros de préprocesseur négligentes.
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 qu'il ne peut pas être aussi difficile d'utiliser simplement std::cin
Et de simplement diffuser des données dans un programme.