Apa yang terjadi dengan 'mendapat (stdin)' di situs coderbyte?

144

Coderbyte adalah situs tantangan pengkodean online (saya menemukannya hanya 2 menit yang lalu).

Tantangan C ++ pertama yang disambut dengan Anda memiliki kerangka C ++ yang perlu Anda ubah:

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

}

Jika Anda sedikit terbiasa dengan C ++, hal pertama * yang muncul di mata Anda adalah:

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

Jadi, ok, panggilan kode getsyang sudah usang sejak C ++ 11 dan dihapus sejak C ++ 14 yang buruk itu sendiri.

Tapi kemudian saya menyadari: getsadalah tipe char*(char*). Jadi ia seharusnya tidak menerima FILE*parameter dan hasilnya seharusnya tidak dapat digunakan sebagai pengganti intparameter, tapi ... tidak hanya mengkompilasi tanpa peringatan atau kesalahan, tetapi itu berjalan dan benar-benar melewati nilai input yang benar FirstFactorial.

Di luar situs khusus ini, kode tidak dikompilasi (seperti yang diharapkan), jadi apa yang terjadi di sini?


* Sebenarnya yang pertama adalah using namespace stdtetapi itu tidak relevan dengan masalah saya di sini.

bolov
sumber
Perhatikan bahwa stdindi perpustakaan standar adalah a FILE*, dan penunjuk ke tipe apa pun dikonversi menjadi char*, yang merupakan tipe argumen dari gets(). Namun, Anda tidak boleh, pernah, pernah menulis kode seperti itu di luar kontes C yang membingungkan. Jika kompiler Anda bahkan menerimanya, tambahkan lebih banyak bendera peringatan, dan jika Anda mencoba untuk memperbaiki basis kode yang memiliki konstruksi di dalamnya, ubah peringatan menjadi kesalahan.
Davislor
1
@ Davidvis no it not "fungsi kandidat tidak dapat dijalankan: tidak ada konversi yang diketahui dari 'struct _IO_FILE *' ke 'char *' untuk argumen 1"
bolov
3
@ Davidvish, itu mungkin benar untuk C kuno, tapi jelas tidak untuk C ++.
Quentin
@ Quentin Ya. Itu seharusnya tidak dikompilasi. Tantangan yang dituju mungkin adalah, "Ambil kode yang rusak ini, baca pikiranku tentang apa yang seharusnya dilakukan, dan perbaiki," tetapi dalam kasus itu harus ada spesifikasi nyata. Dengan kasus uji.
Davislor
6
Saya terkejut tidak ada yang mencoba ini, tetapi gets(stdin )(dengan ruang ekstra) menghasilkan kesalahan C ++ yang diharapkan.
Roman Odaisky

Jawaban:

174

Saya adalah pendiri Coderbyte dan juga orang yang menciptakan retasan ini gets(stdin).

Komentar pada posting ini benar bahwa itu adalah bentuk mencari-dan-ganti, jadi izinkan saya menjelaskan mengapa saya melakukan ini dengan sangat cepat.

Kembali pada hari ketika saya pertama kali membuat situs (sekitar 2012), itu hanya mendukung JavaScript. Tidak ada cara untuk "membaca input" di JavaScript yang berjalan di browser, dan jadi akan ada fungsi foo(input)dan saya menggunakan readline()fungsi dari Node.js untuk menyebutnya seperti foo(readline()). Kecuali saya masih anak-anak dan tidak tahu yang lebih baik, jadi saya benar-benar hanya diganti readline()dengan input pada saat run-time. Jadi foo(readline())menjadi foo(2)atau foo("hello")yang berfungsi dengan baik untuk JavaScript.

Sekitar 2013/2014 saya menambahkan lebih banyak bahasa dan menggunakan layanan pihak ketiga untuk mengevaluasi kode online, tetapi sangat sulit untuk melakukan stdin / stdout dengan layanan yang saya gunakan, jadi saya terjebak dengan pencarian dan penggantian konyol yang sama untuk bahasa seperti Python, Ruby, dan akhirnya C ++, C #, dll.

Maju cepat ke hari ini, saya menjalankan kode di wadah saya sendiri, tetapi tidak pernah memperbarui cara stdin / stdout bekerja karena orang sudah terbiasa dengan hack aneh (beberapa orang bahkan memposting di forum yang menjelaskan bagaimana cara mengatasinya).

Saya tahu ini bukan praktik terbaik dan tidak membantu bagi seseorang yang belajar bahasa baru untuk melihat retasan seperti ini, tetapi idenya adalah bagi programmer baru untuk tidak khawatir membaca input sama sekali dan hanya fokus pada penulisan algoritma untuk menyelesaikan masalah. Satu keluhan umum tentang pengkodean situs tantangan bertahun-tahun yang lalu adalah bahwa programmer baru akan menghabiskan banyak waktu hanya mencari tahu cara membaca dari stdinatau membaca baris dari file, jadi saya ingin coders baru untuk menghindari masalah ini pada Coderbyte.

Saya akan segera memperbarui seluruh halaman editor bersama dengan kode default dan stdinmembaca untuk bahasa. Semoga programmer C ++ akan menikmati menggunakan Coderbyte lebih lanjut :)

Daniel Borowski
sumber
20
"[B] ut idenya adalah untuk programmer baru untuk tidak khawatir tentang membaca input sama sekali dan hanya fokus pada penulisan algoritma untuk memecahkan masalah" - dan itu tidak terpikir oleh Anda, daripada menulis sesuatu yang menyerupai "nyata "kode, cukup taruh nama fungsi yang dibuat atau placeholder yang jelas di tempat itu? Sangat penasaran.
Ruther Rendommeleigh
25
Saya benar-benar tidak berharap saya akan memilih jawaban selain dari saya sendiri ketika saya memposting ini. Terima kasih telah membuktikan kesalahan saya dengan cara yang hebat. Sangat menyenangkan melihat jawaban Anda.
bolov
4
Sangat menarik! Saya akan merekomendasikan, jika Anda ingin tetap hack ini, bahwa Anda mengganti panggilan fungsi dengan sesuatu seperti TAKE_INPUT, kemudian gunakan find-replace Anda untuk memasukkan #define TAKE_INPUT whatever_heredi bagian atas.
Draconis
18
Kami membutuhkan lebih banyak jawaban dimulai dengan "Saya adalah pendiri x dan juga orang yang menciptakan ini" .
pipa
2
@iheanyi Tidak ada yang memintanya menjadi sempurna. Bahkan, saya yakin bahwa hampir semua penampung akan lebih baik daripada sesuatu yang terlihat seperti kode yang valid untuk setiap pemula tetapi sebenarnya tidak dikompilasi.
Ruther Rendommeleigh
112

Saya tertarik. Jadi, waktu untuk mengenakan kacamata investigasi dan karena saya tidak memiliki akses ke kompiler atau bendera kompilasi saya perlu mendapatkan inventif. Juga karena tidak ada tentang kode ini masuk akal itu bukan pertanyaan ide buruk setiap asumsi.

Pertama mari kita periksa jenis sebenarnya gets. Saya punya sedikit trik untuk itu:

template <class> struct Name;

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

Dan itu terlihat ... 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.

getsditandai sebagai usang dan memiliki tanda tangan char *(char *). Tapi bagaimana cara FirstFactorial(gets(stdin));kompilasi?

Mari kita coba yang lain:

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

Yang memberi kita:

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

Akhirnya kita mendapatkan sesuatu: decltype(8). Jadi semuanya gets(stdin)diganti secara tekstual dengan input ( 8).

Dan hal-hal semakin aneh. Kesalahan kompiler berlanjut:

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

Jadi sekarang kita mendapatkan kesalahan yang diharapkan untuk cout << FirstFactorial(gets(stdin));

Saya memeriksa makro dan karena #undef getssepertinya tidak melakukan apa-apa sepertinya itu bukan makro.

Tapi

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

Itu mengkompilasi.

Tapi

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

Tidak dengan kesalahan yang diharapkan di n2telepon.

Dan lagi, hampir semua modifikasi untuk mainmembuat garis cout << FirstFactorial(gets(stdin));meludahkan kesalahan yang diharapkan.

Apalagi yang stdintampak kosong.

Jadi saya hanya bisa menyimpulkan dan berspekulasi mereka memiliki program kecil yang mem-parsing sumber dan mencoba (buruk) untuk mengganti gets(stdin)dengan nilai input test case sebelum benar-benar memasukkannya ke dalam kompiler. Jika ada yang punya teori yang lebih baik atau benar-benar tahu apa yang mereka lakukan, silakan bagikan!

Ini jelas merupakan praktik yang sangat buruk. Saat meneliti ini saya menemukan setidaknya ada pertanyaan di sini ( contoh ) tentang ini dan karena orang tidak tahu bahwa ada situs di luar sana yang melakukan ini, jawaban mereka adalah "jangan gunakan getsgunakan ... sebagai gantinya" yang memang saran yang bagus tetapi lebih membingungkan OP karena usaha membaca yang valid dari stdin akan gagal di situs ini.


TLDR

gets(stdin)tidak valid C ++. Ini tipuan yang digunakan situs ini (untuk alasan apa saya tidak tahu). Jika Anda ingin terus mengirimkan di situs (saya tidak mendukungnya atau tidak mendukungnya) Anda harus menggunakan konstruksi ini yang jika tidak tidak masuk akal, tetapi perlu diketahui bahwa itu rapuh. Hampir semua modifikasi untuk mainmemuntahkan kesalahan. Di luar situs ini menggunakan metode membaca input normal.

bolov
sumber
27
Saya benar-benar kagum. Mungkin T / A ini bisa menjadi posting kanonik tentang mengapa tidak belajar dari pengkodean situs tantangan.
ubah igel
28
Sesuatu yang sangat jahat sedang terjadi, dan saya pikir itu pada tingkat penggantian teks dalam kode sumber di luar kompiler. Coba ini: std::cout << "gets(stdin)";dan hasilnya adalah 8(atau apa pun yang Anda ketik di bidang 'input'. Ini adalah penyalahgunaan bahasa yang memalukan.
ubah igel
14
@Stobor perhatikan tanda kutip di sekitar "gets(stdin)". Itu string literal yang bahkan preprocessor tidak akan menyentuh
ubah igel
2
Mengutip James Kirk: "Ini aneh sekali."
ApproachingDarknessFish
2
@alterigel turun dari kuda tinggi Anda. Ini bukan pernyataan apakah belajar dari coding situs tantangan bermanfaat atau tidak. Siapa Anda untuk memutuskan bagaimana orang mempraktikkan hal-hal?
Matsemann
66

Saya mencoba tambahan berikut maindalam editor Coderbyte:

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

Di mana potongan misterius dan misterius gets(stdin)muncul di dalam string literal. Ini seharusnya tidak boleh diubah oleh apa pun, bahkan preprocessor, dan setiap programmer C ++ harus mengharapkan kode ini untuk mencetak string yang tepat gets(stdin)ke output standar. Namun kita melihat output berikut, ketika dikompilasi dan dijalankan pada coderbyte:

8

Di mana nilai 8diambil langsung dari bidang 'input' yang mudah di bawah editor.

Kode ajaib

Dari sini, jelaslah bahwa editor online ini melakukan operasi pencarian dan ganti buta pada kode sumber, penampilan pengganti gets(stdin)dengan 'input' pengguna. Saya pribadi menyebut ini penyalahgunaan bahasa yang lebih buruk daripada makro preprosesor yang ceroboh.

Dalam konteks situs web tantangan pengkodean online, saya khawatir dengan hal ini karena mengajarkan praktik yang tidak konvensional, tidak standar, tidak berarti, dan setidaknya tidak aman seperti gets(stdin), dan dengan cara yang tidak dapat diulang pada platform lain.

Saya yakin itu tidak bisa ini sulit untuk hanya menggunakan std::cindan hanya input stream untuk program.

ubah igel
sumber
dan itu bahkan bukan buta "menemukan dan mengganti" karena kadang-kadang itu menggantikannya kadang tidak.
bolov
4
@bolov mungkinkah hanya kejadian pertama gets(stdin)yang diganti? Maksud saya 'buta' dalam arti bahwa itu tampaknya tidak mengetahui sintaks atau tata bahasa bahasa.
ubah igel
ya kamu benar. Ini menggantikan kejadian pertama. Saya mencoba menempatkan satu sebelum utama dan memang itulah yang saya dapatkan.
bolov
1
Penelitian lebih lanjut menunjukkan bahwa situs itu melakukannya untuk semua bahasa, bukan hanya C ++ - python / ruby ​​ia menggunakan pemanggilan fungsi ("raw_input ()" atau "STDIN.gets") yang biasanya akan mengembalikan string dari stdin, tetapi akhirnya melakukan sebagai pengganti string itu. Saya kira menemukan kecocokan regex untuk fungsi getline terlalu sulit, jadi mereka pergi dengan mendapat (stdin) untuk C / C ++.
Stobor
4
@Stobor dang, kau benar. Saya dapat mengkonfirmasi ini terjadi untuk Java juga, garis System.out.print(FirstFactorial(s.nextLine()9));mencetak 89bahkan ketika stidak ditentukan.
ubah igel