Làm thế nào để mã C in từ 1 đến 1000 mà không có vòng lặp hoặc câu lệnh điều kiện hoạt động?


148

Tôi đã tìm thấy Cin từ 1 đến 1000 mà không có vòng lặp hoặc điều kiện : Nhưng tôi không hiểu cách thức hoạt động của nó. Bất cứ ai cũng có thể đi qua mã và giải thích từng dòng?

#include <stdio.h>
#include <stdlib.h>

void main(int j) {
  printf("%d\n", j);
  (&main + (&exit - &main)*(j/1000))(j+1);
}

1
Bạn đang biên dịch thành C hay C ++? Bạn thấy lỗi gì? Bạn không thể gọi mainbằng C ++.
ninjalj

@ninjalj tôi đã tạo ra một C ++ Dự án và sao chép / qua các mã lỗi là: bất hợp pháp, trái toán hạng có kiểu 'void (__cdecl *) (int) và biểu hiện phải là một con trỏ tới một loại đối tượng hoàn chỉnh
ob_dev

1
@ninjalj Các mã này đang hoạt động trên ideone.org nhưng không có trong studio hình ảnh ideone.com/MtJ1M
ob_dev

@oussama tương tự, nhưng nhẹ hơn khó hiểu: ideone.com/2ItXm bạn đang chào đón. :)
Đánh dấu

2
tôi đã xóa tất cả các ký tự '&' khỏi các dòng này (& main + (& exit - & main) * (j / 1000)) (j + 1); và mã này vẫn hoạt động.
ob_dev

Câu trả lời:


264

Đừng bao giờ viết mã như thế.


j<1000, j/1000là 0 (chia số nguyên). Vì thế:

(&main + (&exit - &main)*(j/1000))(j+1);

tương đương với:

(&main + (&exit - &main)*0)(j+1);

Đó là:

(&main)(j+1);

Mà gọi mainvới j+1.

Nếu j == 1000, sau đó các dòng tương tự đi ra như:

(&main + (&exit - &main)*1)(j+1);

Mà sôi xuống

(&exit)(j+1);

Đó là exit(j+1)và rời khỏi chương trình.


(&exit)(j+1)exit(j+1)về cơ bản là giống nhau - trích dẫn C99 §6.3.2.1 / 4:

Trình chỉ định hàm là một biểu thức có kiểu hàm. Ngoại trừ khi đó là toán hạng của toán tử sizeof hoặc toán tử unary & toán tử , một trình chỉ định hàm với kiểu " kiểu trả về hàm " được chuyển đổi thành một biểu thức có kiểu " con trỏ tới kiểu trả về hàm ".

exitlà một chỉ định chức năng. Ngay cả khi không có &địa chỉ đơn vị của toán tử, nó được coi là một con trỏ để hoạt động. ( &Chỉ làm cho nó rõ ràng.)

Và các lệnh gọi hàm được mô tả trong §6.5.2.2 / 1 và sau đây:

Biểu thức biểu thị hàm được gọi sẽ có con trỏ kiểu để hàm trả về void hoặc trả về một loại đối tượng khác với kiểu mảng.

Vì vậy, exit(j+1)hoạt động vì chuyển đổi tự động loại chức năng thành loại con trỏ sang chức năng và (&exit)(j+1)hoạt động tốt với chuyển đổi rõ ràng sang loại con trỏ sang chức năng.

Điều đó đang được nói, đoạn mã trên không tuân thủ ( maincó hai đối số hoặc không có gì cả), và &exit - &main, tôi tin rằng, không được xác định theo §6.5.6 / 9:

Khi hai con trỏ bị trừ, cả hai sẽ trỏ đến các phần tử của cùng một đối tượng mảng hoặc một phần tử qua phần tử cuối cùng của đối tượng mảng; ...

Bản thân phần bổ sung (&main + ...)sẽ có giá trị và có thể được sử dụng, nếu số lượng được thêm bằng 0, vì §6.5.6 / 7 nói:

Đối với mục đích của các toán tử này, một con trỏ tới một đối tượng không phải là một phần tử của một mảng hoạt động giống như một con trỏ tới phần tử đầu tiên của một mảng có chiều dài với một loại đối tượng là kiểu phần tử của nó.

Vì vậy, thêm số 0 vào &mainsẽ ổn (nhưng không sử dụng nhiều).


4
foo(arg)(&foo)(arg)tương đương, họ gọi foo với đối số arg. newty.de/fpt/fpt.html là một trang thú vị về con trỏ hàm.
Mat

1
@Krishnabhadra: trong trường hợp đầu tiên, foolà một con trỏ, &foolà địa chỉ của con trỏ đó. Trong trường hợp thứ hai, foolà một mảng và &footương đương với foo.
Mat

8
Phức tạp không cần thiết, ít nhất là cho C99:((void(*[])()){main, exit})[j / 1000](j + 1);
Per Johansson

1
&fookhông giống như fookhi nói đến một mảng. &foolà một con trỏ tới mảng, foolà một con trỏ tới phần tử đầu tiên. Họ có cùng giá trị mặc dù. Đối với các hàm, fun&funcả hai con trỏ tới hàm.
Per Johansson

1
FYI, nếu bạn nhìn vào câu trả lời có liên quan cho câu hỏi khác được trích dẫn ở trên , bạn sẽ thấy có một biến thể thực sự là C99 phù hợp. Đáng sợ, nhưng sự thật.
Daniel Pryden

41

Nó sử dụng đệ quy, số học con trỏ và khai thác hành vi làm tròn của phép chia số nguyên.

Các j/1000vòng dài xuống đến 0 cho tất cả j < 1000; một lần jđạt 1000, nó ước tính là 1.

Bây giờ nếu bạn có a + (b - a) * n, ở đâu nlà 0 hoặc 1, bạn kết thúc bằng aif n == 0bif n == 1. Sử dụng &main(địa chỉ của main()) và &exitcho ab, thuật ngữ (&main + (&exit - &main) * (j/1000))trả về &mainkhi jdưới 1000, &exitnếu không. Con trỏ hàm kết quả sau đó được cung cấp đối số j+1.

Toàn bộ cấu trúc này dẫn đến hành vi đệ quy: trong khi jdưới 1000, maintự gọi đệ quy; khi jđạt tới 1000, nó gọi exitthay thế, làm cho chương trình thoát với mã thoát 1001 (loại này bẩn, nhưng hoạt động).


1
Câu trả lời tốt, nhưng một nghi ngờ..Làm thế nào thoát chính với mã thoát 1001? Chính không trả lại bất cứ điều gì..Bất kỳ giá trị trả về mặc định?
Krishnabhadra

2
Khi j đạt 1000, main không tái diễn vào chính nó nữa; thay vào đó, nó gọi hàm libc exit, lấy mã thoát làm đối số của nó và, tốt, thoát khỏi quy trình hiện tại. Tại thời điểm đó, j là 1000, vì vậy j + 1 bằng 1001, trở thành mã thoát.
tdammers
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.