Что происходит с «gets (stdin)» на сайте кодер-байтом? - PullRequest
136 голосов
/ 20 марта 2019

Coderbyte - это онлайновый сайт, посвященный программированию (я нашел его всего 2 минуты назад).

Первый вызов C ++ , с которым вас встретили, имеет скелет C ++, который нужно изменить:

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

}

Если вы мало знакомы с C ++, первое, что вы увидите *:

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

Так, хорошо, код вызывает gets, который устарел с C ++ 11 и удален с C ++ 14, что само по себе плохо.

Но тогда я понимаю: gets имеет тип char*(char*). Поэтому он не должен принимать параметр FILE*, и результат не должен использоваться вместо параметра int, но ... он не только компилируется без каких-либо предупреждений или ошибок, но и запускается и фактически передает введите правильное значение FirstFactorial.

За пределами этого конкретного сайта код не компилируется (как и ожидалось), так что здесь происходит?


* На самом деле первый - using namespace std, но это не имеет отношения к моей проблеме здесь.

Ответы [ 3 ]

160 голосов
/ 21 марта 2019

Я - основатель Coderbyte, а также парень, который создал этот gets(stdin) хак.

Комментарии к этому сообщению верны, что это форма поиска и замены, поэтому позвольте мнеобъясните, почему я сделал это очень быстро.

В тот день, когда я впервые создал сайт (около 2012 года), он поддерживал только JavaScript.В JavaScript не было возможности «читать входные данные», работающие в браузере, и поэтому была бы функция foo(input), и я использовал функцию readline() из Node.js, чтобы вызывать ее как foo(readline()).За исключением того, что я был ребенком и не знал лучше, поэтому я буквально заменил readline() на ввод во время выполнения.Таким образом, foo(readline()) стал foo(2) или foo("hello"), который отлично работал для JavaScript.

В 2013/2014 гг. Я добавил больше языков и использовал сторонний сервис для оценки кода в Интернете, но это было очень трудно сделатьstdin / stdout со службами, которые я использовал, поэтому я застрял с тем же глупым поиском и заменой для языков, как Python, Ruby и, в конечном счете, C ++, C # и т. д.код в моих собственных контейнерах, но я никогда не обновлял способ работы stdin / stdout, потому что люди привыкли к странному хаку (некоторые люди даже публиковали сообщения на форумах, объясняющие, как его обойти).

Я знаю этоЭто не лучшая практика, и для кого-то, изучающего новый язык, бесполезно видеть подобные хаки, но идея для новых программистов была в том, чтобы вообще не беспокоиться о чтении ввода и просто сосредоточиться на написании алгоритма для решения проблемы.Много лет назад одна распространенная жалоба на сайты с проблемами кодирования заключалась в том, что новые программисты тратят много времени, просто выясняя, как читать из stdin или читать строки из файла, поэтому я хотел, чтобы новые кодеры избежали этой проблемы на Coderbyte.

Я скоро обновлю всю страницу редактора вместе с кодом по умолчанию и чтением stdin для языков.Надеемся, что тогда программисты на C ++ получат больше удовольствия от использования Coderbyte:)

110 голосов
/ 20 марта 2019

Я заинтригован. Итак, пришло время надеть защитные очки, и поскольку у меня нет доступа к компилятору или флагам компиляции, мне нужно проявить изобретательность. Кроме того, поскольку ничего в этом коде не имеет смысла, это не плохая идея, ставящая под сомнение каждое предположение.

Сначала давайте проверим фактический тип gets. У меня есть маленькая хитрость для этого:

template <class> struct Name;

int main() { 

    Name<decltype(gets)> n;

  // keep this function call here
  cout << FirstFactorial(gets(stdin));
  return 0;

}

И это выглядит ... нормально:

/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 помечен как устаревший и имеет подпись char *(char *). Но тогда как компиляция FirstFactorial(gets(stdin));?

Давайте попробуем что-нибудь еще:

int main() { 
  Name<decltype(gets(stdin))> n;

  // keep this function call here
  cout << FirstFactorial(gets(stdin));
  return 0;

} 

Что дает нам:

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

Наконец-то мы получаем что-то: decltype(8). Таким образом, весь текст gets(stdin) был заменен на текстовый ввод (8).

И все становится страннее. Ошибка компилятора продолжается:

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

Итак, теперь мы получаем ожидаемую ошибку для cout << FirstFactorial(gets(stdin));

Я проверил макрос и, поскольку #undef gets, похоже, ничего не делает, похоже, что это не макрос.

Но

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

Компилируется.

Но

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

Нет с ожидаемой ошибкой в ​​строке n2.

И снова, почти любое изменение main заставляет строку cout << FirstFactorial(gets(stdin)); выплевывать ожидаемую ошибку.

Более того, stdin на самом деле кажется пустым.

Таким образом, я могу только заключить и предположить, что у них есть небольшая программа, которая анализирует исходный код и пытается (плохо) заменить gets(stdin) входным значением тестового примера перед его фактической подачей в компилятор. Если кто-то имеет лучшую теорию или действительно знает, что он делает, пожалуйста, поделитесь!

Это, очевидно, очень плохая практика. Исследуя это, я обнаружил, что здесь есть, по крайней мере, вопрос ( пример ) об этом, и потому что люди не имеют представления о том, что существует сайт, который делает это, их ответ «не используйте gets» используйте ... вместо этого ", который действительно является хорошим советом, но только больше сбивает с толку OP, так как любая попытка корректного чтения из stdin потерпит неудачу на этом сайте.


TLDR

gets(stdin) является недействительным C ++. Это трюк, который использует этот конкретный сайт (по каким причинам я не могу понять). Если вы хотите продолжить отправку на сайт (я не одобряю и не одобряю его), вы должны использовать эту конструкцию, которая в противном случае не имела бы смысла, но помните, что она хрупкая. Почти любые изменения в main приведут к ошибке. За пределами этого сайта используйте обычные методы чтения ввода.

65 голосов
/ 20 марта 2019

Я попробовал следующее дополнение к main в редакторе Coderbyte:

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

Где таинственный и загадочный фрагмент gets(stdin) появляется внутри строкового литерала. Это не должно быть преобразовано ничем, даже препроцессором, и любой программист C ++ должен ожидать, что этот код выведет точную строку gets(stdin) в стандартный вывод. И все же мы видим следующий вывод при компиляции и запуске на кодербайте:

8

Где значение 8 берется прямо из удобного поля ввода в редакторе.

Magic code

Из этого ясно, что этот онлайн-редактор выполняет слепые операции поиска и замены над исходным кодом, заменив gets(stdin) на пользовательский «ввод». Лично я бы назвал это неправильным использованием языка, который хуже небрежного макроса препроцессора.

В контексте веб-сайта, посвященного проблемам кодирования, меня это беспокоит, потому что он учит нетрадиционным, нестандартным, бессмысленным и, по крайней мере, небезопасным методам, таким как gets(stdin), и таким образом это не может повторяться на других платформах.

Я уверен, что это не может быть , это трудно просто использовать std::cin и просто передавать данные в программу.

...