2012-04-25 19 views
17

Câu hỏi sau đây được đưa ra trong cuộc thi lập trình đại học. Chúng tôi được yêu cầu đoán đầu ra và/hoặc giải thích nó hoạt động. Không cần phải nói, không ai trong chúng ta thành công.Hiểu một đối số không phổ biến cho chính

main(_){write(read(0,&_,1)&&main());} 

Một số Googling ngắn dẫn tôi đến câu hỏi này chính xác, hỏi trong codegolf.stackexchange.com:

https://codegolf.stackexchange.com/a/1336/4085

Ở đó, nó giải thích nó: Reverse stdin and place on stdout, nhưng không cách.

Tôi cũng tìm thấy một số giúp đỡ trong vấn đề này: Three arguments to main, and other obfuscating tricks nhưng nó vẫn không giải thích như thế nào main(_), &_&&main() công trình.

Câu hỏi của tôi là, cách cú pháp làm việc như thế nào? Có phải họ là điều tôi nên biết, như trong, họ vẫn có liên quan?

tôi sẽ biết ơn đối với bất kỳ con trỏ (để liên kết tài nguyên, vv), nếu không nói là câu trả lời hoàn toàn.

+0

Chương trình đó sẽ không biên dịch trong C++. Đang xóa thẻ C++. –

+0

@ Robᵩ Ah cảm ơn bạn. Tôi đã bất cẩn. – RaunakS

+10

Ngay cả trong C, chương trình đó gọi hành vi không xác định theo nhiều cách. Kết quả là có thể dự đoán chỉ cho các trình biên dịch cụ thể nhắm mục tiêu các loại CPU cụ thể (ngay cả trên codegolf, chương trình này chỉ làm điều gì đó thú vị ở mức tối ưu hóa cụ thể). Câu trả lời đúng cho "Chương trình này làm gì?" bao gồm "Nó phụ thuộc", "Bất cứ điều gì nó muốn," và "Nó khiến bạn bị sa thải." –

Trả lời

26

Chương trình này làm gì?

main(_){write(read(0,&_,1)&&main());} 

Trước khi chúng ta phân tích nó, chúng ta hãy tô điểm nó:

main(_) { 
    write (read(0, &_, 1) && main()); 
} 

Trước tiên, bạn nên biết rằng _ là một tên biến hợp lệ, mặc dù là một xấu xí.Hãy thay đổi nó:

main(argc) { 
    write(read(0, &argc, 1) && main()); 
} 

Tiếp theo, nhận ra rằng kiểu trả về của một hàm, và loại một tham số là tùy chọn trong C (nhưng không phải trong C++):

int main(int argc) { 
    write(read(0, &argc, 1) && main()); 
} 

Tiếp theo, hiểu như thế nào giá trị trả về hoạt động. Đối với một số loại CPU nhất định, giá trị trả về luôn được lưu trữ trong cùng sổ đăng ký (ví dụ: EAX trên x86). Do đó, nếu bạn bỏ qua câu lệnh return, giá trị trả lại là có khả năng sẽ trở thành chức năng mới nhất được trả lại.

int main(int argc) { 
    int result = write(read(0, &argc, 1) && main()); 
    return result; 
} 

Các cuộc gọi đến read là nhiều hay ít rõ ràng: nó đọc từ tiêu chuẩn trong (file descriptor 0), vào bộ nhớ nằm ở &argc, cho 1 byte. Nó trả về 1 nếu đọc thành công và 0 ngược lại.

&& là toán tử logic "và". Nó đánh giá phía bên tay phải của nó nếu và chỉ khi nó nằm bên trái là "true" (về mặt kỹ thuật, bất kỳ giá trị khác 0). Kết quả của biểu thức &&int luôn là 1 (cho "true") hoặc 0 (đối với false).

Trong trường hợp này, phía bên tay phải gọi main không có đối số. Gọi main không có đối số sau khi khai báo nó với 1 đối số là hành vi không xác định. Tuy nhiên, nó thường hoạt động, miễn là bạn không quan tâm đến giá trị ban đầu của tham số argc.

Kết quả của số && sau đó được chuyển đến write(). Vì vậy, mã của chúng tôi bây giờ trông giống như:

int main(int argc) { 
    int read_result = read(0, &argc, 1) && main(); 
    int result = write(read_result); 
    return result; 
} 

Hmm. Xem nhanh các trang hướng dẫn cho thấy rằng write có ba đối số chứ không phải một đối số. Một trường hợp khác của hành vi không xác định. Cũng giống như gọi main với quá ít đối số, chúng tôi không thể dự đoán những gì write sẽ nhận được đối số thứ 2 và thứ 3 của nó. Trên máy tính thông thường, họ sẽ nhận được một cái gì đó, nhưng chúng tôi không thể biết chắc chắn những gì. (Trên các máy tính không điển hình, những điều kỳ lạ có thể xảy ra.) Tác giả dựa trên write nhận bất kỳ thứ gì trước đây được lưu trữ trên ngăn xếp bộ nhớ. Và, anh ta dựa vào rằng là đối số thứ 2 và thứ 3 để đọc.

int main(int argc) { 
    int read_result = read(0, &argc, 1) && main(); 
    int result = write(read_result, &argc, 1); 
    return result; 
} 

Sửa cuộc gọi không hợp lệ để main, và thêm tiêu đề, và mở rộng && ta có:

#include <unistd.h> 
int main(int argc, int argv) { 
    int result; 
    result = read(0, &argc, 1); 
    if(result) result = main(argc, argv); 
    result = write(result, &argc, 1); 
    return result; 
} 


Kết luận

Chương trình này sẽ không hoạt động như mong đợi trên nhiều máy tính. Ngay cả khi bạn sử dụng cùng một máy tính với tư cách là tác giả gốc, nó có thể không hoạt động trên một hệ điều hành khác. Ngay cả khi bạn sử dụng cùng một máy tính và cùng một hệ điều hành, nó sẽ không hoạt động trên nhiều trình biên dịch. Ngay cả khi bạn sử dụng cùng một trình biên dịch máy tính và hệ điều hành, nó có thể không hoạt động nếu bạn thay đổi cờ dòng lệnh của trình biên dịch.

Như tôi đã nói trong phần nhận xét, câu hỏi không có câu trả lời hợp lệ.Nếu bạn tìm thấy người tổ chức cuộc thi hoặc thẩm phán cuộc thi nói cách khác, đừng mời họ tham gia cuộc thi tiếp theo của bạn.

+1

Oh wow, đó là rất, _very_ toàn diện. Làm rõ: cú pháp 'write()' là 'int write (int fd, char * Buff, int NumBytes)'. Vì vậy, giá trị trả về của 'read()' đang trở thành '1' cho việc ghi vào đầu ra tiêu chuẩn? – RaunakS

+1

0 là đầu vào tiêu chuẩn, 1 là đầu ra tiêu chuẩn, 2 là lỗi chuẩn. Vì vậy, một sự trở lại thành công từ đọc (kết hợp với một sự trở lại thành công từ các cuộc gọi đệ quy đến chính) mang lại một ghi để stdout. Một thất bại trở lại từ kết quả đọc trong một ghi vào stdin. Đó là một hành vi chưa được xác định khác. –

+0

Ah vâng, tôi nên wiki trước khi hỏi. Mã này sẽ là một đối thủ IOCCC rất tốt. Và là hành vi không xác định như sao chép này? Tôi có nghĩa là, trên cùng một trình biên dịch (gcc 4.4.1), điều này sẽ luôn luôn cho kết quả tương tự? – RaunakS

8

Ok, _ chỉ là một biến được khai báo ở đầu K & R C cú pháp với loại mặc định là int. Nó hoạt động như bộ nhớ tạm thời.

Chương trình sẽ cố gắng đọc một byte từ đầu vào tiêu chuẩn. Nếu có đầu vào, nó sẽ gọi đệ quy chính tiếp tục đọc một byte.

Khi kết thúc đầu vào, read(2) sẽ trả về 0, biểu thức sẽ trả về 0, cuộc gọi hệ thống write(2) sẽ thực thi và chuỗi cuộc gọi có thể sẽ được giải phóng.

Tôi nói "có thể" ở đây vì từ thời điểm này trên các kết quả phụ thuộc rất nhiều vào việc triển khai thực hiện. Các thông số khác để write(2) đang thiếu, nhưng một cái gì đó sẽ nằm trong thanh ghi và trên stack, vì vậy một cái gì đó sẽ được chuyển vào kernel. Cùng một hành vi không xác định áp dụng cho giá trị trả về từ các kích hoạt đệ quy khác nhau của main.

Trên máy Mac x86_64 của tôi, chương trình đọc đầu vào tiêu chuẩn cho đến EOF và sau đó thoát, không viết gì cả.

+0

Bất kỳ trích dẫn nào về '_' là gì? Tò mò để biết về nó –

+0

Nó chỉ là một tham số chính thức * ("biến") * tên. Nó tương đương với 'main (int _)' ... tưởng tượng rằng chúng gọi nó là "argc" * và tất cả sẽ rõ ràng. Đó là: 'main (argc)' sẽ là C sớm với mặc định ** int, ** các khai báo * prototype * đã được thêm vào sau. Họ không tuyên bố thông thường * argv * nhưng không có gì quyết liệt sẽ xảy ra như một kết quả. – DigitalRoss

+0

Vâng, một '_' đơn giản là tên biến hợp pháp. –

Các vấn đề liên quan