¿Qué está pasando con 'gets (stdin)' en el sitio coderbyte?

144

Coderbyte es un sitio de desafío de codificación en línea (lo encontré hace solo 2 minutos).

El primer desafío de C ++ con el que te encuentras tiene un esqueleto de C ++ que debes modificar:

#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 está poco familiarizado con C ++, lo primero * que aparece en sus ojos es:

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

Entonces, ok, el código llama, getsque está en desuso desde C ++ 11 y se elimina desde C ++ 14, que es malo en sí mismo.

Pero luego me doy cuenta: getses de tipo char*(char*). Por lo tanto, no debería aceptar un FILE*parámetro y el resultado no debería ser utilizable en lugar de un intparámetro, sino que ... no solo se compila sin advertencias o errores, sino que se ejecuta y realmente le pasa el valor de entrada correcto FirstFactorial.

Fuera de este sitio en particular, el código no se compila (como se esperaba), entonces, ¿qué está pasando aquí?


* En realidad, el primero es using namespace stdpero eso es irrelevante para mi problema aquí.

bolov
fuente
Tenga stdinen cuenta que en la biblioteca estándar hay un FILE*, y un puntero a cualquier tipo se convierte char*, que es el tipo de argumento de gets(). Sin embargo, nunca debes escribir ese tipo de código fuera de un concurso de C ofuscado. Si su compilador incluso lo acepta, agregue más indicadores de advertencia, y si está tratando de arreglar una base de código que tiene esa construcción, convierta las advertencias en errores.
Davislor
1
@Davislor no, no "la función candidata no es viable: no se conoce la conversión de 'struct _IO_FILE *' a 'char *' para el primer argumento"
bolov
3
@Davislor eh, eso podría ser cierto para la antigua C, pero definitivamente no para C ++.
Quentin
@Quentin Sí. Eso no debería compilar. El desafío deseado podría haber sido: "Toma este código roto, lee mi mente sobre lo que se supone que debe hacer y arréglalo", pero en ese caso debería haber una especificación real. Con casos de prueba.
Davislor
66
Me sorprende que nadie haya intentado esto, pero gets(stdin )(con un espacio extra) produce el error esperado de C ++.
Roman Odaisky

Respuestas:

174

Soy el fundador de Coderbyte y también el tipo que creó este gets(stdin)truco.

Los comentarios en esta publicación son correctos de que es una forma de buscar y reemplazar, así que permítanme explicar por qué hice esto realmente rápido.

En el día en que creé el sitio por primera vez (alrededor de 2012), solo admitía JavaScript. No había forma de "leer en la entrada" en JavaScript que se ejecuta en el navegador, por lo que habría una función foo(input)y utilicé la readline()función de Node.js para llamarla así foo(readline()). Excepto que era un niño y no lo sabía mejor, así que literalmente lo reemplacé readline()con la entrada en tiempo de ejecución. Así se foo(readline())convirtió foo(2)o foo("hello")que funcionó bien para JavaScript.

Alrededor de 2013/2014 agregué más idiomas y utilicé un servicio de terceros para evaluar el código en línea, pero fue muy difícil hacer stdin / stdout con los servicios que estaba usando, así que me quedé con el mismo tonto de buscar y reemplazar idiomas. como Python, Ruby y eventualmente C ++, C #, etc.

Avance rápido hasta hoy, ejecuto el código en mis propios contenedores, pero nunca actualicé la forma en que funciona stdin / stdout porque la gente se ha acostumbrado al truco extraño (algunas personas incluso han publicado en foros explicando cómo solucionarlo).

Sé que no es la mejor práctica y no es útil para alguien que está aprendiendo un nuevo idioma ver hacks como este, pero la idea era que los nuevos programadores no se preocuparan por leer la entrada y solo se enfocaran en escribir el algoritmo para resolver el problema. problema. Una queja común acerca de la codificación de sitios de desafío hace años fue que los nuevos programadores pasarían mucho tiempo descubriendo cómo leer stdino leer líneas de un archivo, por lo que quería nuevos codificadores para evitar este problema en Coderbyte.

Pronto actualizaré toda la página del editor junto con el código predeterminado y la stdinlectura de idiomas. Esperemos que los programadores de C ++ disfruten más usando Coderbyte :)

Daniel Borowski
fuente
20
"[B] pero la idea era que los nuevos programadores no se preocuparan por leer la entrada en absoluto y solo se enfocaran en escribir el algoritmo para resolver el problema" - y no se te ocurrió hacerlo, en lugar de escribir algo que se parezca "real "código, solo ponga un nombre de función inventado o un marcador de posición obvio en ese lugar? Genuinamente curioso.
Ruther Rendommeleigh
25
Realmente no esperaba que iba a elegir una respuesta diferente a la mía cuando publiqué esto. Gracias por demostrarme que estoy equivocado de una manera tan genial. Es realmente un placer ver tu respuesta.
bolov
44
¡Muy interesante! Recomendaría, si desea mantener este truco, que reemplace la llamada de función con algo como TAKE_INPUT, luego use su find-replace para insertar #define TAKE_INPUT whatever_hereen la parte superior.
Draconis
18
Necesitamos más respuestas comenzando con "Soy el fundador de xy también el tipo que creó esto" .
tubería
2
@iheanyi Nadie pidió que fuera perfecto. De hecho, estoy convencido de que casi cualquier marcador de posición hubiera sido mejor que algo que parece un código válido para cualquier novato pero que en realidad no se compila.
Ruther Rendommeleigh
112

Estoy intrigado. Entonces, es hora de poner las gafas de investigación y, dado que no tengo acceso al compilador o las banderas de compilación, necesito ser inventivo. Además, porque nada de este código tiene sentido, no es una mala idea cuestionar cada supuesto.

Primero, verifiquemos el tipo real de gets. Tengo un pequeño truco para eso:

template <class> struct Name;

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

Y eso se ve ... 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á marcado como obsoleto y tiene la firma char *(char *). Pero entonces, ¿cómo está FirstFactorial(gets(stdin));compilando?

Probemos algo más:

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

Lo que nos da:

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

Finalmente estamos consiguiendo algo: decltype(8). Por lo tanto, todo gets(stdin)se reemplazó textualmente con la entrada ( 8).

Y las cosas se ponen más raras. El error del compilador continúa:

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

Entonces ahora tenemos el error esperado para cout << FirstFactorial(gets(stdin));

Revisé una macro y, como #undef getsparece no hacer nada, parece que no es una macro.

Pero

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

Se compila.

Pero

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

No con el error esperado en la n2línea.

Y nuevamente, casi cualquier modificación mainhace que la línea cout << FirstFactorial(gets(stdin));escupe el error esperado.

Además, la stdinrealidad parece estar vacía.

Por lo tanto, solo puedo concluir y especular que tienen un pequeño programa que analiza la fuente e intenta (mal) reemplazarlo gets(stdin)con el valor de entrada del caso de prueba antes de realmente introducirlo en el compilador. Si alguien tiene una teoría mejor o realmente sabe lo que está haciendo, ¡comparta!

Obviamente, esta es una muy mala práctica. Mientras investigaba esto, descubrí que hay al menos una pregunta aquí ( ejemplo ) sobre esto y porque la gente no tiene idea de que hay un sitio que hace esto, su respuesta es "no use getsusar ... en cambio", que es es un buen consejo, pero solo confunde más al OP ya que cualquier intento de una lectura válida de stdin fallará en este sitio.


TLDR

gets(stdin)es inválido C ++. Es un truco que utiliza este sitio en particular (por qué razones no puedo entender). Si desea continuar enviando en el sitio (no lo estoy respaldando ni lo estoy respaldando), debe usar esta construcción que de otra manera no tendría sentido, pero tenga en cuenta que es frágil. Casi cualquier modificación a mainescupirá un error. Fuera de este sitio, utilice métodos de lectura de entrada normales.

bolov
fuente
27
Estoy realmente asombrado. Tal vez este Q / A puede ser una publicación canónica sobre por qué no aprender de los sitios de desafío de codificación.
alter igel
28
Algo realmente malo está sucediendo, y creo que está en el nivel de reemplazo de texto en el código fuente fuera del compilador. Prueba esto: std::cout << "gets(stdin)";y la salida es 8(o lo que se teclea en el campo 'entrada' Se trata de un abuso vergonzoso de la lengua..
Igel alter
14
@Stobor tenga en cuenta las citas "gets(stdin)". Esa es una cadena literal que incluso el preprocesador no tocaría
alter igel
2
Para citar a James Kirk: "Esto es muy peculiar".
ApproachingDarknessFish
2
@alterigel baje de su caballo alto. Esto no es una declaración de si aprender de los sitios de desafío de codificación es útil o no. ¿Quién eres tú para decidir cómo la gente practica cosas?
Matsemann
66

Intenté la siguiente adición mainen el editor de Coderbyte:

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

Donde el fragmento misterioso y enigmático gets(stdin)aparece dentro de una cadena literal. Esto no debería ser transformado por nada, ni siquiera el preprocesador, y cualquier programador de C ++ debería esperar que este código imprima la cadena exacta gets(stdin)a la salida estándar. Y sin embargo, vemos el siguiente resultado, cuando se compila y se ejecuta en coderbyte:

8

Donde el valor 8 se toma directamente del conveniente campo de 'entrada' debajo del editor.

Código mágico

A partir de esto, está claro que este editor en línea está realizando operaciones ciegas de buscar y reemplazar en el código fuente, apariencias de sustitución gets(stdin)con la 'entrada' del usuario. Personalmente, llamaría a esto un mal uso del lenguaje que es peor que las macros de preprocesadores descuidados.

En el contexto de un sitio web de desafío de codificación en línea, estoy preocupado por esto porque enseña poco convencional, no estándar, sin sentido y al menos inseguras prácticas gusta gets(stdin), y de una manera que no puede ser repetido en otras plataformas.

Estoy seguro de que no puede ser tan difícil de usar std::ciny simplemente transmitir la entrada a un programa.

alter igel
fuente
y ni siquiera es un "encontrar y reemplazar" ciego porque a veces lo reemplaza a veces no.
bolov
44
@bolov ¿podría ser solo la primera vez gets(stdin)que se reemplaza eso? Quise decir "ciego" en el sentido de que parece no ser consciente de la sintaxis o gramática del lenguaje.
alter igel
Sí, tiene usted razón. Reemplaza la primera ocurrencia. Traté de poner uno antes de main y eso es lo que obtuve de hecho.
bolov
1
La investigación adicional sugiere que ese sitio lo hace para todos los lenguajes, no solo C ++ - python / ruby, usa la llamada a la función ("raw_input ()" o "STDIN.gets") que normalmente devolvería una cadena de stdin, pero termina haciendo una sustitución de cadena de esa cadena en su lugar. Supongo que encontrar una coincidencia de expresiones regulares para la función getline fue demasiado difícil, por lo que eligieron gets (stdin) para C / C ++.
Stobor
44
@Stobor dang, tienes razón. Puedo confirmar que esto también sucede para Java, la línea se System.out.print(FirstFactorial(s.nextLine()9));imprime 89incluso cuando sno está definida.
alter igel