Điều gì đang xảy ra với 'got (stdin)' trên trang web coderbyte?


144

Coderbyte là một trang web thử thách mã hóa trực tuyến (tôi đã tìm thấy nó chỉ 2 phút trước).

Thử thách C ++ đầu tiên mà bạn được chào đón có bộ xương C ++ bạn cần sửa đổi:

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

}

Nếu bạn không quen thuộc với C ++, điều đầu tiên * xuất hiện trong mắt bạn là:

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

Vì vậy, ok, các cuộc gọi mã getsbị từ chối từ C ++ 11 và bị loại bỏ khỏi C ++ 14, bản thân nó không tốt.

Nhưng sau đó tôi nhận ra: getslà loại char*(char*). Vì vậy, nó không nên chấp nhận một FILE*tham số và kết quả không thể sử dụng được ở vị trí của inttham số, nhưng ... không chỉ nó biên dịch mà không có bất kỳ cảnh báo hoặc lỗi nào, nhưng nó chạy và thực sự chuyển giá trị đầu vào chính xác FirstFactorial.

Bên ngoài trang web cụ thể này, mã không biên dịch (như mong đợi), vậy điều gì đang xảy ra ở đây?


* Trên thực tế, điều đầu tiên là using namespace stdnhưng điều đó không liên quan đến vấn đề của tôi ở đây.


Lưu ý rằng stdintrong thư viện chuẩn là một FILE*và một con trỏ tới bất kỳ loại nào chuyển đổi thành char*, đó là loại đối số của gets(). Tuy nhiên, bạn không bao giờ nên viết loại mã đó bên ngoài một cuộc thi C khó hiểu. Nếu trình biên dịch của bạn thậm chí chấp nhận nó, hãy thêm nhiều cờ cảnh báo và nếu bạn đang cố sửa một cơ sở mã có cấu trúc đó, hãy biến cảnh báo thành lỗi.
Davislor

1
@Davislor không có "chức năng ứng cử viên không khả thi: không chuyển đổi được biết đến từ 'struct _IO_FILE *' thành 'char *' cho đối số thứ nhất"
bolov

3
@Davislor huh, điều đó có thể đúng với C cổ đại, nhưng chắc chắn không phải với C ++.
Quentin

@Quentin Vâng. Điều đó không nên biên dịch. Thử thách dự định có thể là, Nhận lấy mã bị hỏng này, đọc suy nghĩ của tôi về những gì nó phải làm và khắc phục nó, nhưng trong trường hợp đó cần phải có một đặc điểm kỹ thuật thực sự. Với các trường hợp thử nghiệm.
Davislor

6
Tôi ngạc nhiên không ai thử điều này, nhưng gets(stdin )(có thêm dung lượng) tạo ra lỗi C ++ dự kiến.
La Mã Odaisky

Câu trả lời:


174

Tôi là người sáng lập Coderbyte và cũng là người đã tạo ra bản gets(stdin)hack này .

Các ý kiến ​​về bài đăng này là chính xác rằng nó là một hình thức tìm và thay thế, vì vậy hãy để tôi giải thích lý do tại sao tôi làm điều này thực sự nhanh chóng.

Ngày trước khi tôi tạo trang web lần đầu tiên (khoảng năm 2012), nó chỉ hỗ trợ JavaScript. Không có cách nào để "đọc đầu vào" trong JavaScript đang chạy trong trình duyệt và do đó sẽ có một hàm foo(input)và tôi đã sử dụng readline()hàm từ Node.js để gọi nó như thế nào foo(readline()). Ngoại trừ tôi là một đứa trẻ và không biết rõ hơn, vì vậy tôi thực sự chỉ thay thế readline()bằng đầu vào vào thời gian chạy. Vì vậy, foo(readline())đã trở thành foo(2)hoặc foo("hello")hoạt động tốt cho JavaScript.

Khoảng 2013/2014 tôi đã thêm nhiều ngôn ngữ và sử dụng dịch vụ của bên thứ ba để đánh giá mã trực tuyến, nhưng rất khó thực hiện stdin / stdout với các dịch vụ tôi đang sử dụng, vì vậy tôi bị mắc kẹt với cùng một ngôn ngữ tìm và thay thế ngớ ngẩn như Python, Ruby và cuối cùng là C ++, C #, v.v.

Nhanh chóng đến hôm nay, tôi chạy mã trong các thùng chứa của riêng mình, nhưng không bao giờ cập nhật cách hoạt động của stdin / stdout vì mọi người đã quen với vụ hack kỳ lạ (một số người thậm chí đã đăng lên các diễn đàn giải thích cách khắc phục nó).

Tôi biết đó không phải là cách thực hành tốt nhất và nó không hữu ích cho ai đó học một ngôn ngữ mới để thấy những bản hack như thế này, nhưng ý tưởng là để các lập trình viên mới không lo lắng về việc đọc đầu vào và chỉ tập trung vào viết thuật toán để giải quyết vấn đề. Một khiếu nại phổ biến về các trang web thách thức mã hóa cách đây nhiều năm là các lập trình viên mới sẽ dành nhiều thời gian để tìm ra cách đọc stdinhoặc đọc các dòng từ một tệp, vì vậy tôi muốn các lập trình viên mới tránh được vấn đề này trên Coderbyte.

Tôi sẽ sớm cập nhật toàn bộ trang soạn thảo cùng với mã mặc định và stdinđọc ngôn ngữ. Hy vọng sau đó các lập trình viên C ++ sẽ thích sử dụng Coderbyte hơn :)


20
"[B] ut ý tưởng là để các lập trình viên mới không lo lắng về việc đọc đầu vào và chỉ tập trung vào viết thuật toán để giải quyết vấn đề" - và nó đã không xảy ra với bạn, thay vì viết một cái gì đó giống như "thực "Mã, chỉ cần đặt một tên hàm tạo thành hoặc một trình giữ chỗ rõ ràng ở vị trí đó? Thực sự tò mò.
Ruther Rendommeleigh 21/03/19

25
Tôi thực sự không mong đợi rằng tôi sẽ chọn một câu trả lời khác hơn là của riêng tôi khi tôi đăng bài này. Cảm ơn bạn đã chứng minh tôi sai theo cách tuyệt vời như vậy. Thật sự rất vui khi thấy câu trả lời của bạn.
bolov

4
Rất thú vị! Tôi muốn khuyên bạn, nếu bạn muốn giữ bản hack này, bạn nên thay thế cuộc gọi chức năng bằng một cái gì đó giống như TAKE_INPUT, sau đó sử dụng find-thay thế của bạn để chèn #define TAKE_INPUT whatever_hereở trên cùng.
Draconis

18
Chúng tôi cần nhiều câu trả lời hơn bắt đầu với "Tôi là người sáng lập x và cũng là người đã tạo ra điều này" .
ống

2
@iheanyi Không ai yêu cầu nó phải hoàn hảo. Trên thực tế, tôi tin rằng hầu như bất kỳ trình giữ chỗ nào sẽ tốt hơn thứ gì đó trông giống như mã hợp lệ cho bất kỳ người mới nào nhưng không thực sự biên dịch.
Ruther Rendommeleigh

112

Tôi tò mò. Vì vậy, thời gian để đặt kính điều tra và vì tôi không có quyền truy cập vào trình biên dịch hoặc cờ biên dịch, tôi cần có được sáng tạo. Ngoài ra bởi vì không có gì về mã này có nghĩa là nó không phải là một câu hỏi ý tưởng tồi mỗi giả định.

Trước tiên hãy kiểm tra loại thực tế gets. Tôi có một mẹo nhỏ cho việc đó:

template <class> struct Name;

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

Và điều đó có vẻ ... bình thường:

/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được đánh dấu là không dùng nữa và có chữ ký char *(char *). Nhưng sau đó là FirstFactorial(gets(stdin));biên dịch như thế nào ?

Hãy thử một cái gì đó khác:

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

Cung cấp cho chúng tôi:

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

Cuối cùng, chúng tôi nhận được một cái gì đó : decltype(8). Vì vậy, toàn bộ gets(stdin)đã được thay thế bằng văn bản với đầu vào ( 8).

Và những điều nhận được weirder. Lỗi trình biên dịch tiếp tục:

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

Vì vậy, bây giờ chúng tôi nhận được lỗi dự kiến ​​cho cout << FirstFactorial(gets(stdin));

Tôi đã kiểm tra một macro và vì #undef getsdường như không làm gì cả, có vẻ như nó không phải là một macro.

Nhưng

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

Nó biên dịch.

Nhưng

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

Không có lỗi dự kiến ​​tại n2dòng.

Và một lần nữa, gần như bất kỳ sửa đổi nào để mainlàm cho dòng cout << FirstFactorial(gets(stdin));phát sinh lỗi dự kiến.

Hơn nữa, stdinthực tế dường như là trống rỗng.

Vì vậy, tôi chỉ có thể kết luận và suy đoán rằng họ có một chương trình nhỏ phân tích nguồn và cố gắng (kém) để thay thế gets(stdin)bằng giá trị đầu vào của trường hợp thử nghiệm trước khi thực sự đưa nó vào trình biên dịch. Nếu bất cứ ai có một lý thuyết tốt hơn hoặc thực sự biết những gì họ đang làm xin vui lòng chia sẻ!

Đây rõ ràng là một thực tế rất xấu. Trong khi nghiên cứu điều này, tôi thấy có ít nhất một câu hỏi ở đây ( ví dụ ) về điều này và bởi vì mọi người không biết rằng có một trang web ngoài đó, câu trả lời của họ là "không sử getsdụng ... thay vào đó" thực sự là một lời khuyên tốt nhưng chỉ khiến OP bối rối hơn vì mọi nỗ lực đọc hợp lệ từ stdin sẽ thất bại trên trang web này.


TLD

gets(stdin)C ++ không hợp lệ. Đây là một mánh lới quảng cáo mà trang web cụ thể này sử dụng (vì lý do tôi không thể tìm ra). Nếu bạn muốn tiếp tục gửi trên trang web (tôi không xác nhận nó cũng không xác nhận nó), bạn phải sử dụng cấu trúc này nếu không sẽ không có ý nghĩa gì, nhưng hãy lưu ý rằng nó dễ vỡ. Hầu như bất kỳ sửa đổi nào mainsẽ gây ra lỗi. Bên ngoài trang web này sử dụng phương pháp đọc đầu vào bình thường.


27
Tôi thực sự ngạc nhiên. Có lẽ Q / A này có thể là một bài viết kinh điển về lý do tại sao không học hỏi từ các trang web thách thức mã hóa.
thay đổi igel

28
Một cái gì đó thực sự xấu xa đang xảy ra, và tôi nghĩ nó ở cấp độ thay thế văn bản trong mã nguồn bên ngoài trình biên dịch. Hãy thử điều này: std::cout << "gets(stdin)";và đầu ra là 8(hoặc bất cứ điều gì bạn nhập vào trường 'đầu vào'. Đây là một sự lạm dụng ngôn ngữ ô nhục.
thay đổi igel

14
@Stobor lưu ý các trích dẫn xung quanh "gets(stdin)". Đó là một chuỗi ký tự mà ngay cả bộ tiền xử lý sẽ không chạm
thay đổi igel

2
Trích lời James Kirk: "Đây là đặc thù chết tiệt."
Tiếp

2
@alterigel xuống ngựa cao của bạn. Đây không phải là tuyên bố cho việc học từ các trang web thách thức mã hóa có hữu ích hay không. Bạn là ai để quyết định cách mọi người thực hành công cụ?
Matsemann

66

Tôi đã thử bổ sung sau mainvào trình soạn thảo Coderbyte:

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

Nơi đoạn trích bí ẩn và bí ẩn gets(stdin)xuất hiện bên trong một chuỗi ký tự. Điều này không nên được chuyển đổi bởi bất cứ điều gì, ngay cả bộ tiền xử lý và bất kỳ lập trình viên C ++ nào cũng mong muốn mã này sẽ in chuỗi chính xác gets(stdin)đến đầu ra tiêu chuẩn. Tuy nhiên, chúng ta thấy đầu ra sau đây, khi được biên dịch và chạy trên coderbyte:

8

Trong đó giá trị 8được lấy thẳng từ trường 'đầu vào' thuận tiện trong trình chỉnh sửa.

Mã ma thuật

Từ điều này, rõ ràng trình soạn thảo trực tuyến này đang thực hiện các hoạt động tìm và thay thế mù trên mã nguồn, sự xuất hiện thay thế gets(stdin)của 'đầu vào' của người dùng. Cá nhân tôi sẽ gọi đây là sự lạm dụng ngôn ngữ tồi tệ hơn các macro tiền xử lý bất cẩn.

Trong bối cảnh của một trang web thử thách mã hóa trực tuyến, tôi lo lắng về điều này bởi vì nó dạy các cách thực hành độc đáo, không chuẩn, vô nghĩa và ít nhất là không an toàn như gets(stdin), và theo cách không thể lặp lại trên các nền tảng khác.

Tôi chắc chắn rằng nó không thể này khó có thể chỉ cần sử dụng std::cinvà chỉ dòng đầu vào cho một chương trình.


và nó thậm chí không phải là một "tìm và thay thế" mù quáng bởi vì đôi khi nó thay thế nó đôi khi không.
bolov

4
@bolov có thể chỉ là sự xuất hiện đầu tiên của gets(stdin)nó được thay thế? Tôi có nghĩa là 'mù' theo nghĩa là nó dường như không biết về cú pháp hoặc ngữ pháp của ngôn ngữ.
thay đổi igel

vâng, bạn đúng Nó thay thế sự xuất hiện đầu tiên. Tôi đã thử đặt một cái trước chính và đó là những gì tôi thực sự có được.
bolov

1
Nghiên cứu sâu hơn cho thấy rằng trang web đó làm điều đó cho tất cả các ngôn ngữ, không chỉ C ++ - python / ruby ​​mà nó sử dụng lệnh gọi hàm ("raw_input ()" hoặc "STDIN.gets") thường trả về một chuỗi từ stdin, nhưng cuối cùng lại thực hiện thay thế một chuỗi của chuỗi đó. Tôi đoán việc tìm kiếm một kết hợp regex cho hàm getline là quá khó, vì vậy họ đã đi với get (stdin) cho C / C ++.
Stobor

4
@Stobor dang, bạn nói đúng. Tôi cũng có thể xác nhận điều này xảy ra với Java, dòng System.out.print(FirstFactorial(s.nextLine()9));in 89ngay cả khi skhông được xác định.
thay đổi igel
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.