Что происходит с «gets (stdin)» на сайте кодер-байтом?

144

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но это не имеет отношения к моей проблеме здесь.

Болов
источник
Обратите внимание, что stdinв стандартной библиотеке есть FILE*, и указатель на любой тип преобразуется в char*, который является типом аргумента gets(). Тем не менее, вы никогда не должны писать такого рода код вне запутанного конкурса Си. Если ваш компилятор даже принимает его, добавьте больше флагов предупреждений, и, если вы пытаетесь исправить кодовую базу с этой конструкцией, превратите предупреждения в ошибки.
Дэвислор
1
@Davislor нет, это не «функция-кандидат недопустима: нет известного преобразования из« struct _IO_FILE * »в« char * »для 1-го аргумента»
bolov
3
@Davislor, да, это может быть правдой для древнего C, но определенно не для C ++.
Квентин
@Quentin Да. Это не должно компилироваться. Задача могла заключаться в следующем: «Возьми этот испорченный код, прочитай мои мысли о том, что он должен делать, и исправь его», но в этом случае должна быть настоящая спецификация. С тестовыми случаями.
Дэвислор
6
Я удивлен, что никто не пробовал это, но gets(stdin )(с дополнительным пробелом) выдает ожидаемую ошибку C ++.
Роман Одайский

Ответы:

174

Я основатель 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на изучение того, как читать или читать строки из файла, поэтому я хотел, чтобы новые кодеры избежали этой проблемы на кодербайте.

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

Даниэль Боровски
источник
20
«[B] но идея была в том, чтобы новые программисты вообще не беспокоились о чтении ввода и просто сосредоточились на написании алгоритма для решения проблемы» - и вам не пришло в голову, вместо того, чтобы писать что-то, что напоминает «реальный» msgstr "код, просто поместите имя выдуманной функции или очевидный заполнитель в этом месте? Искренне любопытно
Рутер Рэндом Мели
25
Я искренне не ожидал, что я собирался выбрать ответ, отличный от моего, когда я отправил это. Спасибо за то, что доказали мне неправоту таким замечательным способом. Это действительно приятно видеть ваш ответ.
Болов
4
Очень интересно! Я бы порекомендовал, если вы хотите сохранить этот взлом, чтобы вы заменили вызов функции на что-то вроде TAKE_INPUT, а затем используйте ваш find-replace для вставки #define TAKE_INPUT whatever_hereвверху.
Драконис
18
Нам нужно больше ответов, начиная с «Я основатель X, а также парень, который создал это» .
труба
2
@iheanyi Никто не просил, чтобы это было идеально. На самом деле, я убежден, что почти любой заполнитель был бы лучше, чем что-то, что выглядит как правильный код для любого новичка, но на самом деле не компилируется.
Рутер Рэндом Мели
112

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

Сначала давайте проверим фактический тип 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)текст был заменен на input ( 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)его входным значением тестового примера перед тем, как фактически передать его в компилятор. Если кто-то имеет лучшую теорию или действительно знает, что он делает, пожалуйста, поделитесь!

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


TLDR

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

Болов
источник
27
Я искренне поражен. Может быть, это Q / A может быть каноническим постом о том, почему бы не учиться на сайтах проблем кодирования.
alter igel
28
Что-то действительно плохое происходит, и я думаю, что это на уровне замены текста в исходном коде вне компилятора. Попробуйте это: std::cout << "gets(stdin)";и вывод будет 8(или все, что вы вводите в поле ввода). Это позорное злоупотребление языком.
alter igel
14
@Stobor обратите внимание на цитаты вокруг "gets(stdin)". Это строковый литерал, который не коснется даже препроцессор
alter igel
2
Цитирую Джеймса Кирка: «Это чертовски странно».
Приближается к
2
@alterigel сойди со своей высокой лошади. Это не заявление о том, полезно ли обучение на сайтах с задачами кодирования или нет. Кто ты такой, чтобы решать, как люди практикуют вещи?
Мацеманн
66

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

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

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

8

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

Магический код

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

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

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

Alter Igel
источник
и это даже не слепое «найди и замени», потому что иногда оно заменяет его, иногда нет.
Болов
4
@bolov это могло быть только первое появление того, gets(stdin)что заменено? Я имел в виду «слепой» в том смысле, что он, кажется, не знает синтаксиса или грамматики языка.
alter igel
да ты прав. Он заменяет первый случай. Я попытался поставить один перед основным, и это то, что я действительно получил.
Болов
1
Дальнейшие исследования показывают, что этот сайт делает это для всех языков, не только для C ++ - python / ruby ​​использует функцию («raw_input ()» или «STDIN.gets»), которая обычно возвращает строку из stdin, но в итоге выполняет замена строки этой строки вместо этого. Я думаю, найти совпадение с регулярным выражением для функции getline было слишком сложно, поэтому они пошли с get (stdin) для C / C ++.
Stobor
4
@ Стабор черт, ты прав. Я могу подтвердить, что это происходит и для Java, строка System.out.print(FirstFactorial(s.nextLine()9));печатается, 89даже если sона не определена.
alter igel