Що відбувається з "get (stdin)" на кодербайті сайту?


144

Coderbyte - це веб-сайт із викликом кодування в Інтернеті (я знайшов його лише 2 хвилини тому).

Перший виклик 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в стандартній бібліотеці є a FILE*, а вказівник на будь-який тип перетворюється на char*, який є типом аргументу gets(). Однак ви ніколи і ніколи ніколи не повинні писати такий код поза закритим конкурсом C. Якщо ваш компілятор навіть приймає це, додайте більше попереджувальних прапорів, і якщо ви намагаєтеся виправити базу коду, яка містить цю конструкцію, перетворіть попередження в помилки.
Девіслор

1
@Davislor no it does "функція кандидата не життєздатна: невідома конверсія з '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чи читати рядки з файлу, тому я хотів, щоб нові кодери уникали цієї проблеми на Coderbyte.

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


20
"[B] ut Ідея полягала в тому, щоб нові програмісти взагалі не турбувалися про читання даних і просто зосередилися на написанні алгоритму для вирішення проблеми" - і вам не спало на думку замість того, щоб написати щось, що нагадує "справжнє "код, просто помістіть складене ім'я функції або очевидний заповнювач у цьому місці? По-справжньому цікаво.
Резер Рендлімелей

25
Я щиро не очікував, що збираюся вибрати іншу, ніж свою, коли я опублікував цю відповідь. Дякую за те, що ви довели мене неправильно таким чудовим чином. Дуже приємно бачити вашу відповідь.
болов

4
Дуже цікаво! Я рекомендую, якщо ви хочете зберегти цей злом, замінити виклик функції чимось на кшталт TAKE_INPUT, а потім використати ваш пошук-заміну, щоб вставити #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)було текстово замінено на введення ( 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використання ... замість", що справді хороша порада, але лише більше плутає ОП, оскільки будь-яка спроба дійсного читання з stdin на цьому сайті буде невдалою.


TLDR

gets(stdin)недійсний C ++. Цей твір використовує цей трюк (з яких причин я не можу з’ясувати). Якщо ви хочете продовжувати надсилати на сайті (я ні його не схвалюю, ні не схвалюю), ви повинні використовувати цю конструкцію, яка в іншому випадку не мала б сенсу, але пам’ятайте, що вона крихка. Практично будь-які модифікації mainвиплюнуть помилку. Поза межами цього сайту використовуйте звичайні методи зчитування вводу.


27
Я щиро вражений. Можливо, це питання / питання може стати канонічним повідомленням про те, чому б не навчитися кодувати викликові сайти.
alter igel

28
Щось справді зло відбувається, і я думаю, що це відбувається на рівні заміни тексту у вихідному коді поза компілятором. Спробуйте це: std::cout << "gets(stdin)";і результат є 8(або що ви вводите в поле "введення". Це ганебне зловживання мовою.
alter igel

14
@Stobor відзначте цитати навколо "gets(stdin)". Це рядковий буквал, який навіть препроцесор не чіпав би
змінити igel

2
Процитуйте Джеймса Кірка: "Це чорт особливо."
Наближення

2
@alterigel зійди з твого високого коня. Це не є твердженням, корисне чи ні навчання з веб-сайтів, що займаються кодуванням. Хто ти такий, щоб вирішити, як люди практикують речі?
Мацеманн

66

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

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

Там, де загадковий і загадковий фрагмент gets(stdin)з'являється всередині рядка-літералу. Це, можливо, не має перетворюватися нічим, навіть препроцесором, і будь-який програміст C ++ повинен очікувати, що цей код надрукує точний рядок gets(stdin)на стандартний вихід. І все ж ми бачимо наступний вихід при компіляції та запуску на кодербайте:

8

Якщо значення 8взято прямо з зручного поля "введення" під редактором.

Магічний код

З цього зрозуміло, що цей онлайн-редактор виконує операції пошуку та заміни сліпих у вихідному коді, заміни зовнішнього вигляду gets(stdin)користувача "введенням". Я б особисто назвав це неправильним використанням мови, що гірше, ніж недбалі макроси препроцесора.

У контексті веб-сайту виклику кодування в Інтернеті мене це турбує, оскільки він вчить нетрадиційним, нестандартним, безглуздим і, принаймні, небезпечним практикам, як gets(stdin), і таким чином, який не можна повторити на інших платформах.

Я впевнений, що просто використовувати та просто передавати введення в програму не може бути таким важким std::cin.


і це навіть не сліпе "знайти і заміни", бо іноді це замінює, іноді - ні.
болов

4
@bolov чи може це бути лише першим явищем, gets(stdin)яке замінено? Я мав на увазі «сліпий» у тому сенсі, що, здається, він не знає синтаксису чи граматики мови.
alter igel

так, ти маєш рацію. Він замінює перше виникнення. Я спробував поставити його перед головним, і ось що я отримав насправді.
болов

1
Подальші дослідження говорять про те, що цей сайт робить це для всіх мов, а не тільки для C ++ - python / ruby, він використовує функцію виклику ("raw_input ()" або "STDIN.gets"), яка, як правило, повертає рядок з stdin, але в кінцевому підсумку робить замість рядка замість цього рядка. Я думаю, що відповідність регулярного вираження для функції getline було занадто важким, тому вони пішли з get (stdin) для C / C ++.
Стобор

4
@Stobor dang, ти маєш рацію. Я можу підтвердити, що це трапляється і для Java. Рядок System.out.print(FirstFactorial(s.nextLine()9));друкується 89навіть тоді, коли він sне визначений.
alter igel
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.