O que está acontecendo com o 'gets (stdin)' no site coderbyte?

144

O Coderbyte é um site de desafio de codificação on-line (eu o encontrei há apenas 2 minutos).

O primeiro desafio de C ++ que você recebe é um esqueleto de C ++ que você precisa 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;

}

Se você está pouco familiarizado com C ++, a primeira coisa * que aparece em seus olhos é:

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

Então, ok, o código chama getsobsoleto desde C ++ 11 e removido desde C ++ 14, o que é ruim por si só.

Mas então eu percebo: getsé do tipo char*(char*). Portanto, ele não deve aceitar um FILE*parâmetro e o resultado não deve ser utilizado no lugar de um intparâmetro, mas ... não apenas compila sem avisos ou erros, mas executa e passa o valor de entrada correto para FirstFactorial.

Fora deste site específico, o código não é compilado (como esperado), então o que está acontecendo aqui?


* Na verdade, o primeiro é, using namespace stdmas isso é irrelevante para o meu problema aqui.

Bolov
fonte
Observe que stdinna biblioteca padrão há FILE*ae um ponteiro para qualquer tipo é convertido em char*, que é o tipo do argumento de gets(). No entanto, você nunca deve nunca escrever esse tipo de código fora de um concurso ofuscado em C. Se o seu compilador o aceitar, adicione mais sinalizadores de aviso e, se você estiver tentando corrigir uma base de código com essa construção, transforme os avisos em erros.
21419 Davislor
1
@Davislor não, não "função candidato não viável: a conversão não é conhecida a partir de '_IO_FILE struct *' para 'char *' para o 1º argumento"
bolov
3
@ Davidis huh, isso pode ser verdade para C antigo, mas definitivamente não para C ++.
Quentin
@Quentin Yeah. Isso não deve compilar. O desafio pretendido poderia ter sido: "Pegue esse código quebrado, leia minha mente sobre o que ele deve fazer e corrija-o", mas nesse caso deve haver uma especificação real. Com casos de teste.
Davislor
6
Estou surpreso que ninguém tenha tentado isso, mas gets(stdin )(com um espaço extra) produz o erro C ++ esperado.
Roman Odaisky

Respostas:

174

Eu sou o fundador da Coderbyte e também o cara que criou esse gets(stdin)hack.

Os comentários nesta postagem estão corretos, dizendo que é uma forma de encontrar e substituir, então deixe-me explicar por que fiz isso rapidamente.

No dia em que criei o site (por volta de 2012), ele era compatível apenas com JavaScript. Não havia como "ler a entrada" no JavaScript em execução no navegador e, portanto, haveria uma função foo(input)e eu usei a readline()função do Node.js para chamá-lo dessa maneira foo(readline()). Exceto que eu era criança e não sabia melhor, então eu literalmente substituí readline()a entrada em tempo de execução. Assim, foo(readline())tornou-se foo(2)ou foo("hello")que funcionou bem para JavaScript.

Por volta de 2013/2014, adicionei mais idiomas e usei serviços de terceiros para avaliar o código on-line, mas era muito difícil fazer stdin / stdout com os serviços que eu estava usando, então fiquei com o mesmo tolo encontrar e substituir para idiomas como Python, Ruby e, eventualmente, C ++, C #, etc.

Avançando hoje, executo o código em meus próprios contêineres, mas nunca atualizei o funcionamento do stdin / stdout porque as pessoas se acostumaram com o hack estranho (algumas pessoas até postaram em fóruns explicando como contorná-lo).

Sei que não é uma prática recomendada e não é útil para alguém que está aprendendo um novo idioma ver hacks como esse, mas a idéia era que os novos programadores não se preocupassem com a leitura de entrada e apenas se concentrassem em escrever o algoritmo para resolver o problema. problema. Uma queixa comum sobre sites de desafio de codificação anos atrás era que novos programadores passavam muito tempo apenas descobrindo como ler stdinou ler linhas de um arquivo, então eu queria que novos codificadores evitassem esse problema no Coderbyte.

Em breve, atualizarei a página inteira do editor, juntamente com o código padrão e a stdinleitura para idiomas. Espero que os programadores de C ++ gostem de usar mais o Coderbyte :)

Daniel Borowski
fonte
20
"[A] idéia era que os novos programadores não se preocupassem com a leitura de entrada e apenas se concentrassem em escrever o algoritmo para resolver o problema" - e isso não ocorreu a você, em vez de escrever algo parecido com "real" ", basta colocar um nome de função inventado ou um espaço reservado óbvio nesse local? Genuinamente curioso.
Ruther Rendommeleigh 21/03/19
25
Eu realmente não esperava que eu escolheria uma resposta diferente da minha quando eu publiquei isso. Obrigado por me provar que estou errado de uma maneira excelente. É realmente um prazer ver sua resposta.
bolov
4
Muito interessante! Eu recomendaria, se você deseja manter esse hack, que substitua a chamada de função por algo como TAKE_INPUT, em seguida, use seu find-replace para inserir #define TAKE_INPUT whatever_herena parte superior.
Draconis
18
Precisamos de mais respostas começando com "Eu sou o fundador do x e também o cara que criou isso" .
pipe
2
@iheanyi Ninguém pediu que fosse perfeito. Na verdade, estou convencido de que quase qualquer espaço reservado seria melhor do que algo que parece um código válido para qualquer iniciante, mas na verdade não é compilado.
Ruther Rendommeleigh
112

Estou intrigado. Portanto, é hora de colocar os óculos de investigação e, como não tenho acesso aos sinalizadores de compilação ou compilação, preciso ser criativo. Também porque nada sobre esse código faz sentido, não é uma má idéia questionar todas as suposições.

Primeiro, vamos verificar o tipo real de gets. Eu tenho um pequeno truque para isso:

template <class> struct Name;

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

E isso parece ... 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 descontinuado e tem a assinatura char *(char *). Mas então como está FirstFactorial(gets(stdin));compilando?

Vamos tentar outra coisa:

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

O que nos dá:

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

Finalmente estamos começando algo: decltype(8). Portanto, o todo gets(stdin)foi substituído textualmente pela entrada ( 8).

E as coisas ficam mais estranhas. O erro do compilador 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__;

Então agora obtemos o erro esperado para cout << FirstFactorial(gets(stdin));

Eu verifiquei uma macro e, como #undef getsparece não fazer nada, parece que não é uma macro.

Mas

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

Compila.

Mas

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

Não com o erro esperado na n2linha.

E, novamente, quase qualquer modificação para mainfazer a linha cout << FirstFactorial(gets(stdin));cuspir o erro esperado.

Além disso, o stdinfato parece estar vazio.

Portanto, só posso concluir e especular que eles têm um pequeno programa que analisa a fonte e tenta (mal) substituir gets(stdin)pelo valor de entrada do caso de teste antes de realmente inseri-lo no compilador. Se alguém tiver uma teoria melhor ou realmente souber o que está fazendo, compartilhe!

Obviamente, essa é uma prática muito ruim. Ao pesquisar isso, descobri que há pelo menos uma pergunta aqui ( exemplo ) sobre isso e porque as pessoas não têm idéia de que existe um site por aí que faça isso, sua resposta é "não use o getsuso ... em vez", o que é realmente um bom conselho, mas apenas confunde mais o OP, pois qualquer tentativa de leitura válida da stdin falhará neste site.


TLDR

gets(stdin)é C ++ inválido. É um truque que esse site em particular usa (por quais razões eu não consigo descobrir). Se você deseja continuar enviando no site (eu não estou endossando nem endossando), você deve usar essa construção que de outra forma não faria sentido, mas esteja ciente de que é quebradiça. Quase todas as modificações maincausam erros. Fora deste site, use métodos normais de leitura de entradas.

Bolov
fonte
27
Estou genuinamente impressionado. Talvez este Q / A possa ser um post canônico sobre por que não aprender com sites de desafio de codificação.
Igel Alter
28
Algo realmente ruim está acontecendo, e acho que está no nível de substituição de texto no código-fonte fora do compilador. Tente o seguinte: std::cout << "gets(stdin)";e a saída é 8(ou o que você digitar no campo 'input'. Este é um abuso vergonhoso da linguagem.
alter igel
14
@Stobor observe as aspas "gets(stdin)". Essa é uma string literal que nem o pré-processador tocaria
alter igel
2
Para citar James Kirk: "Isso é muito peculiar".
ApproachingDarknessFish
2
@alterigel saia do seu cavalo alto. Esta não é uma declaração sobre se é útil ou não aprender com os sites de desafio de codificação. Quem é você para decidir como as pessoas praticam coisas?
Matsemann 22/03/19
66

Eu tentei a seguinte adição mainno editor Coderbyte:

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

Onde o trecho misterioso e enigmático gets(stdin)aparece dentro de uma string literal. Isso não deve ser transformado por nada, nem mesmo pelo pré-processador, e qualquer programador C ++ deve esperar que esse código imprima a seqüência exata gets(stdin)na saída padrão. E ainda vemos a seguinte saída, quando compilada e executada em coderbyte:

8

Onde o valor 8é retirado diretamente do conveniente campo 'input' sob o editor.

Código mágico

A partir disso, fica claro que este editor on-line está executando operações de localização e substituição às cegas no código-fonte, aparências de substituição gets(stdin)pela 'entrada' do usuário. Pessoalmente, eu chamaria isso de uso indevido da linguagem, pior do que macros de pré-processador descuidado.

No contexto de um site de desafio de codificação on-line, estou preocupado com isso porque ensina práticas não convencionais, não padronizadas, sem sentido e pelo menos inseguras , como gets(stdin), e de uma maneira que não podem ser repetidas em outras plataformas.

Tenho certeza de que não pode ser tão difícil de usar std::cine apenas transmitir entrada para um programa.

alterar igel
fonte
e nem é um "encontrar e substituir" cego, porque às vezes o substitui, às vezes não.
bolov
4
@ bolov poderia ser apenas a primeira ocorrência de gets(stdin)que é substituído? Eu quis dizer "cego" no sentido de que parece desconhecer a sintaxe ou gramática da linguagem.
Igel Alter
sim você está certo. Ele substitui a primeira ocorrência. Eu tentei colocar um antes do main e foi isso que eu consegui.
bolov
1
Pesquisas posteriores sugerem que o site faz isso para todas as linguagens, não apenas para o C ++ - python / ruby ​​que usa a chamada de função ("raw_input ()" ou "STDIN.gets") que normalmente retornaria uma string do stdin, mas acaba fazendo uma substituição de string dessa string. Eu acho que encontrar uma correspondência de regex para a função getline foi muito difícil, então eles usaram o gets (stdin) para C / C ++.
Stobor
4
@ Stobor dang, você está certo. Posso confirmar que isso também acontece com Java, a linha é System.out.print(FirstFactorial(s.nextLine()9));impressa 89mesmo quando sestá indefinida.
Igel Alter