เกิดอะไรขึ้นกับ 'gets (stdin)' บนไซต์ coderbyte


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()อย่างไรก็ตามคุณไม่ควรเขียนโค้ดชนิดนั้นนอกการประกวด C ที่สับสน หากคอมไพเลอร์ของคุณยอมรับได้ให้เพิ่มการตั้งค่าสถานะการเตือนเพิ่มเติมและหากคุณกำลังพยายามแก้ไขรหัสฐานที่มีโครงสร้างนั้นให้เปลี่ยนคำเตือนเป็นข้อผิดพลาด
Davislor

1
@Davislor ไม่ "ฟังก์ชันผู้สมัครไม่สามารถใช้งานได้: ไม่รู้จักการแปลงจาก 'struct _IO_FILE *' เป็น 'char *' สำหรับอาร์กิวเมนต์ 1"
bolov

3
@Davislor huh นั่นอาจเป็นจริงสำหรับ C โบราณ แต่ไม่แน่นอนสำหรับ C ++
เควนติน

@Quentin ใช่ ไม่ควรรวบรวม ความท้าทายที่ตั้งใจไว้อาจเกิดขึ้น“ เอาโค้ดที่เสียหายนี้อ่านใจของฉันเกี่ยวกับสิ่งที่ควรทำและแก้ไขมัน” แต่ในกรณีนั้นควรมีข้อกำหนดที่แท้จริง กับกรณีทดสอบ
Davislor

6
ฉันประหลาดใจที่ไม่มีใครลองทำสิ่งนี้ แต่gets(stdin )(ด้วยพื้นที่เพิ่มเติม) ทำให้เกิดข้อผิดพลาด C ++ ที่คาดหวัง
Roman Odaisky

คำตอบ:


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] เป็นความคิดสำหรับโปรแกรมเมอร์ใหม่ที่ไม่ต้องกังวลกับการอ่านอินพุตและเพียงแค่มุ่งเน้นการเขียนอัลกอริทึมเพื่อแก้ปัญหา" - และมันไม่ได้เกิดขึ้นกับคุณแทนที่จะเขียนสิ่งที่มีลักษณะ "ของจริง "รหัสแค่ใส่ชื่อฟังก์ชั่นที่สร้างขึ้นหรือตัวยึดที่เห็นได้ชัดในจุดนั้น? อยากรู้อยากเห็นอย่างแท้จริง
Ruther Rendommeleigh

25
ฉันไม่ได้คาดหวังว่าฉันจะเลือกคำตอบที่นอกเหนือจากของฉันเองเมื่อฉันโพสต์ข้อความนี้ ขอบคุณที่พิสูจน์ว่าฉันผิดด้วยวิธีที่ยอดเยี่ยม ดีใจที่ได้เห็นคำตอบของคุณ
bolov

4
น่าสนใจมาก! ฉันอยากจะแนะนำถ้าคุณต้องการเก็บแฮ็คนี้ไว้ให้คุณแทนที่การเรียกใช้ฟังก์ชันด้วยสิ่งที่ชอบTAKE_INPUTแล้วใช้ find-replace ของคุณเพื่อแทรก#define TAKE_INPUT whatever_hereที่ด้านบน
Draconis

18
เราต้องการคำตอบมากขึ้นเริ่มต้นด้วย"ฉันก่อตั้ง x และคนที่สร้างขึ้นนี้"
ไปป์

2
@iheanyi ไม่มีใครขอให้มันสมบูรณ์แบบ ในความเป็นจริงฉันเชื่อว่าเกือบทุกตำแหน่งจะดีกว่าสิ่งที่ดูเหมือนรหัสที่ถูกต้องสำหรับมือใหม่ แต่ไม่ได้รวบรวมจริง ๆ
Ruther Rendommeleigh

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.

getschar *(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จะพ่นข้อผิดพลาดออกไป นอกไซต์นี้ใช้วิธีการอ่านอินพุตปกติ


27
ฉันประหลาดใจอย่างแท้จริง บางที Q / A นี้สามารถโพสต์ที่ยอมรับได้ว่าทำไมไม่เรียนรู้จากการเขียนรหัสเว็บไซต์ท้าทาย
แก้ไข igel

28
มีบางสิ่งที่ชั่วร้ายเกิดขึ้นจริงและฉันคิดว่ามันอยู่ในระดับการแทนที่ข้อความในซอร์สโค้ดด้านนอกคอมไพเลอร์ ลองสิ่งนี้: std::cout << "gets(stdin)";และผลลัพธ์คือ8(หรืออะไรก็ตามที่คุณพิมพ์ลงในช่อง 'อินพุต' นี่เป็นการใช้ภาษาที่น่าอัปยศอดสู
แก้ไข igel

14
@Stobor "gets(stdin)"ทราบคำพูดที่อยู่รอบ ๆ นั่นเป็นตัวอักษรสตริงที่แม้แต่ตัวประมวลผลล่วงหน้าจะไม่แตะต้อง
แก้ไข igel

2
หากต้องการอ้างอิง James Kirk: "นี่คือสิ่งที่แปลกประหลาด"
ApproachDarknessFish

2
@Aterigel ลงจากม้าสูงของคุณ นี่ไม่ใช่คำแถลงว่าการเรียนรู้จากการเขียนโค้ดเว็บไซต์ท้าทายมีประโยชน์หรือไม่ คุณเป็นใครในการตัดสินใจว่าผู้คนปฏิบัติสิ่งต่างๆอย่างไร
Matsemann

66

ฉันลองต่อไปนี้mainในตัวแก้ไข Coderbyte:

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

ที่ตัวอย่างลึกลับและปริศนาgets(stdin)ปรากฏขึ้นภายในสตริงตัวอักษร นี้ไม่ควรอาจจะเปลี่ยนอะไรไม่ได้ preprocessor และใด ๆ C ++ Programmer ควรคาดหวังว่ารหัสนี้เพื่อพิมพ์สตริงที่แน่นอนgets(stdin)ในการออกมาตรฐาน และยังเราเห็นผลลัพธ์ต่อไปนี้เมื่อรวบรวมและทำงานบน coderbyte:

8

ตรงที่ค่า8ถูกนำมาจากฟิลด์ 'อินพุต' ที่สะดวกภายใต้เครื่องมือแก้ไข

รหัสเวทย์มนตร์

จากสิ่งนี้เป็นที่ชัดเจนว่าเครื่องมือแก้ไขออนไลน์นี้ดำเนินการค้นหาและแทนที่แบบปิดตาบนซอร์สโค้ดซึ่งมีการแทนที่gets(stdin)ด้วย 'อินพุต' ของผู้ใช้ โดยส่วนตัวฉันจะเรียกสิ่งนี้ว่าการใช้ภาษาในทางที่ผิดยิ่งกว่ามาโครตัวประมวลผลล่วงหน้าที่ประมาท

ในบริบทของเว็บไซต์ท้าทายการเข้ารหัสออนไลน์ฉันเป็นห่วงเพราะมันสอนวิธีปฏิบัติที่แปลกใหม่ไม่เป็นมาตรฐานไม่มีความหมายและอย่างน้อยก็ไม่ปลอดภัยเช่นgets(stdin)และในลักษณะที่ไม่สามารถทำซ้ำได้บนแพลตฟอร์มอื่น

ฉันแน่ใจว่ามันไม่สามารถนี้เพียงแค่ใช้ยากstd::cinและสตรีมใส่เพียงไปยังโปรแกรม


และไม่ใช่แม้แต่คนตาบอด "ค้นหาและแทนที่" เพราะบางครั้งมันแทนที่บางครั้งก็ไม่ได้
bolov

4
@bolov อาจเป็นเพียงการเกิดขึ้นครั้งแรกgets(stdin)ที่ถูกแทนที่หรือไม่ ฉันหมายถึง 'ตาบอด' ในแง่ที่ดูเหมือนจะไม่รู้ถึงไวยากรณ์หรือไวยากรณ์ของภาษา
เปลี่ยน igel

ใช่คุณถูก. มันแทนที่เกิดขึ้นครั้งแรก ฉันลองใส่ก่อนที่จะหลักและนั่นคือสิ่งที่ฉันได้รับแน่นอน
bolov

1
การวิจัยเพิ่มเติมแสดงให้เห็นว่าเว็บไซต์นั้นใช้สำหรับทุกภาษาไม่ใช่แค่ C ++ - python / ruby ​​โดยใช้การเรียกใช้ฟังก์ชัน ("raw_input ()" หรือ "STDIN.gets") ซึ่งโดยทั่วไปแล้วจะคืนค่าสตริงจาก stdin การทดแทนสตริงของสตริงนั้นแทน ฉันเดาว่าการจับคู่ regex สำหรับฟังก์ชัน getline นั้นยากเกินไปดังนั้นพวกเขาจึงได้รับ (stdin) สำหรับ C / C ++
Stobor

4
@Stobor แดงคุณพูดถูก ฉันสามารถยืนยันสิ่งนี้ได้สำหรับ Java ด้วยเช่นกันเส้นจะSystem.out.print(FirstFactorial(s.nextLine()9));พิมพ์89แม้ในขณะที่sไม่ได้กำหนด
แก้ไข igel
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.