Cuộc thi mã C bị xáo trộn năm 2006. Hãy giải thích sykes2.c


975

Chương trình C này hoạt động như thế nào?

main(_){_^448&&main(-~_);putchar(--_%64?32|-~7[__TIME__-_/8%8][">'txiZ^(~z?"-48]>>";;;====~$::199"[_*2&8|_/64]/(_&2?1:8)%8&1:10);}

Nó biên dịch như nó là (thử nghiệm trên gcc 4.6.3). Nó in thời gian khi biên dịch. Trên hệ thống của tôi:

    !!  !!!!!!              !!  !!!!!!              !!  !!!!!! 
    !!  !!  !!              !!      !!              !!  !!  !! 
    !!  !!  !!              !!      !!              !!  !!  !! 
    !!  !!!!!!    !!        !!      !!    !!        !!  !!!!!! 
    !!      !!              !!      !!              !!  !!  !! 
    !!      !!              !!      !!              !!  !!  !! 
    !!  !!!!!!              !!      !!              !!  !!!!!!

Nguồn: sykes2 - Một đồng hồ trong một dòng , tác giả sykes2 gợi ý

Một số gợi ý: Không có cảnh báo biên dịch cho mỗi mặc định. Được tổng hợp với -Wall, các cảnh báo sau được phát ra:

sykes2.c:1:1: warning: return type defaults to int [-Wreturn-type]
sykes2.c: In function main’:
sykes2.c:1:14: warning: value computed is not used [-Wunused-value]
sykes2.c:1:1: warning: implicit declaration of function putchar [-Wimplicit-function-declaration]
sykes2.c:1:1: warning: suggest parentheses around arithmetic in operand of ‘|’ [-Wparentheses]
sykes2.c:1:1: warning: suggest parentheses around arithmetic in operand of ‘|’ [-Wparentheses]
sykes2.c:1:1: warning: control reaches end of non-void function [-Wreturn-type]

6
Gỡ lỗi: Thêm printf("%d", _);vào phần đầu của mainbản in: pastebin.com/HHhXAYdJ
Corny

Số nguyên, mọi biến số chưa được mặc định làint
drahnr

18
Bạn đã đọc gợi ý? ioccc.org/2006/sykes2/hint.text
nhahtdh


Nếu bạn chạy nó như thế này thì nó gặp sự cố:./a.out $(seq 0 447)
SS Anne

Câu trả lời:


1819

Hãy làm xáo trộn nó.

Thụt lề:

main(_) {
    _^448 && main(-~_);
    putchar(--_%64
        ? 32 | -~7[__TIME__-_/8%8][">'txiZ^(~z?"-48] >> ";;;====~$::199"[_*2&8|_/64]/(_&2?1:8)%8&1
        : 10);
}

Giới thiệu các biến để gỡ rối mớ hỗn độn này:

main(int i) {
    if(i^448)
        main(-~i);
    if(--i % 64) {
        char a = -~7[__TIME__-i/8%8][">'txiZ^(~z?"-48];
        char b = a >> ";;;====~$::199"[i*2&8|i/64]/(i&2?1:8)%8;
        putchar(32 | (b & 1));
    } else {
        putchar(10); // newline
    }
}

Lưu ý rằng -~i == i+1vì bổ sung twos. Vì vậy, chúng tôi có

main(int i) {
    if(i != 448)
        main(i+1);
    i--;
    if(i % 64 == 0) {
        putchar('\n');
    } else {
        char a = -~7[__TIME__-i/8%8][">'txiZ^(~z?"-48];
        char b = a >> ";;;====~$::199"[i*2&8|i/64]/(i&2?1:8)%8;
        putchar(32 | (b & 1));
    }
}

Bây giờ, lưu ý rằng a[b]giống nhưb[a] và áp dụng -~ == 1+thay đổi một lần nữa:

main(int i) {
    if(i != 448)
        main(i+1);
    i--;
    if(i % 64 == 0) {
        putchar('\n');
    } else {
        char a = (">'txiZ^(~z?"-48)[(__TIME__-i/8%8)[7]] + 1;
        char b = a >> ";;;====~$::199"[(i*2&8)|i/64]/(i&2?1:8)%8;
        putchar(32 | (b & 1));
    }
}

Chuyển đổi đệ quy thành một vòng lặp và lén lút đơn giản hơn một chút:

// please don't pass any command-line arguments
main() {
    int i;
    for(i=447; i>=0; i--) {
        if(i % 64 == 0) {
            putchar('\n');
        } else {
            char t = __TIME__[7 - i/8%8];
            char a = ">'txiZ^(~z?"[t - 48] + 1;
            int shift = ";;;====~$::199"[(i*2&8) | (i/64)];
            if((i & 2) == 0)
                shift /= 8;
            shift = shift % 8;
            char b = a >> shift;
            putchar(32 | (b & 1));
        }
    }
}

Điều này xuất ra một ký tự cho mỗi lần lặp. Mỗi ký tự thứ 64, nó xuất ra một dòng mới. Mặt khác, nó sử dụng một cặp bảng dữ liệu để tìm ra cái gì sẽ xuất ra và đặt ký tự 32 (khoảng trắng) hoặc ký tự 33 (a !). Bảng đầu tiên ( ">'txiZ^(~z?") là một bộ gồm 10 bitmap mô tả sự xuất hiện của mỗi ký tự và bảng thứ hai ( ";;;====~$::199") chọn bit thích hợp để hiển thị từ bitmap.

Bảng thứ hai

Hãy bắt đầu bằng cách kiểm tra bảng thứ hai , int shift = ";;;====~$::199"[(i*2&8) | (i/64)];. i/64là số dòng (6 đến 0) và i*2&8là 8 iff ilà 4, 5, 6 hoặc 7 mod 8.

if((i & 2) == 0) shift /= 8; shift = shift % 8chọn chữ số bát phân cao (for i%8= 0,1,4,5) hoặc chữ số bát phân thấp (for i%8= 2,3,6,7) của giá trị bảng. Bảng ca kết thúc trông như thế này:

row col val
6   6-7 0
6   4-5 0
6   2-3 5
6   0-1 7
5   6-7 1
5   4-5 7
5   2-3 5
5   0-1 7
4   6-7 1
4   4-5 7
4   2-3 5
4   0-1 7
3   6-7 1
3   4-5 6
3   2-3 5
3   0-1 7
2   6-7 2
2   4-5 7
2   2-3 3
2   0-1 7
1   6-7 2
1   4-5 7
1   2-3 3
1   0-1 7
0   6-7 4
0   4-5 4
0   2-3 3
0   0-1 7

hoặc ở dạng bảng

00005577
11775577
11775577
11665577
22773377
22773377
44443377

Lưu ý rằng tác giả đã sử dụng bộ kết thúc null cho hai mục nhập bảng đầu tiên (lén lút!).

Điều này được thiết kế sau một màn hình bảy đoạn, với 7s là khoảng trống. Vì vậy, các mục trong bảng đầu tiên phải xác định các phân đoạn được thắp sáng.

Bàn đầu tiên

__TIME__là một macro đặc biệt được xác định bởi bộ tiền xử lý. Nó mở rộng thành một hằng chuỗi chứa thời gian mà bộ tiền xử lý đã chạy, ở dạng "HH:MM:SS". Quan sát rằng nó chứa chính xác 8 ký tự. Lưu ý rằng 0-9 có các giá trị ASCII 48 đến 57 và :có giá trị ASCII 58. Đầu ra là 64 ký tự trên mỗi dòng, do đó để lại 8 ký tự cho mỗi ký tự __TIME__.

7 - i/8%8do đó, chỉ số của __TIME__điều đó hiện đang là đầu ra (điều 7-này là cần thiết bởi vì chúng tôi đang lặp ixuống dưới). Vì vậy, tlà đặc tính của __TIME__đầu ra.

akết thúc bằng cách sau trong nhị phân, tùy thuộc vào đầu vào t:

0 00111111
1 00101000
2 01110101
3 01111001
4 01101010
5 01011011
6 01011111
7 00101001
8 01111111
9 01111011
: 01000000

Mỗi số là một bitmap mô tả các phân đoạn được thắp sáng trong màn hình bảy phân đoạn của chúng tôi. Vì các ký tự đều là ASCII 7 bit, bit cao luôn bị xóa. Vì vậy, 7trong bảng phân khúc luôn in dưới dạng trống. Bảng thứ hai trông như thế này với 7s là khoảng trống:

000055  
11  55  
11  55  
116655  
22  33  
22  33  
444433  

Vì vậy, ví dụ, 401101010(các bit 1, 3, 5 và 6), in ra như

----!!--
!!--!!--
!!--!!--
!!!!!!--
----!!--
----!!--
----!!--

Để cho thấy chúng tôi thực sự hiểu mã, hãy điều chỉnh đầu ra một chút với bảng này:

  00  
11  55
11  55
  66  
22  33
22  33
  44

Điều này được mã hóa như "?;;?==? '::799\x07". Đối với mục đích nghệ thuật, chúng tôi sẽ thêm 64 vào một vài ký tự (vì chỉ sử dụng 6 bit thấp, điều này sẽ không ảnh hưởng đến đầu ra); điều này mang lại "?{{?}}?gg::799G"(lưu ý rằng ký tự thứ 8 không được sử dụng, vì vậy chúng ta thực sự có thể làm cho nó bất cứ điều gì chúng ta muốn). Đặt bảng mới của chúng tôi vào mã gốc:

main(_){_^448&&main(-~_);putchar(--_%64?32|-~7[__TIME__-_/8%8][">'txiZ^(~z?"-48]>>"?{{?}}?gg::799G"[_*2&8|_/64]/(_&2?1:8)%8&1:10);}

chúng tôi nhận được

          !!              !!                              !!   
    !!  !!              !!  !!  !!  !!              !!  !!  !! 
    !!  !!              !!  !!  !!  !!              !!  !!  !! 
          !!      !!              !!      !!                   
    !!  !!  !!          !!  !!      !!              !!  !!  !! 
    !!  !!  !!          !!  !!      !!              !!  !!  !! 
          !!              !!                              !!   

đúng như chúng ta mong đợi Nó không có vẻ ngoài chắc chắn như bản gốc, điều này giải thích tại sao tác giả chọn sử dụng bảng mà anh ta đã làm.


2
@drahnr - Về mặt kỹ thuật, đó là cả *(dereference) và a +: P
gièm pha

18
@ ĐẠI LÝ: Khoảng 30 phút, nhưng tôi đã quay lại và chỉnh sửa nó một chút công bằng. Tôi sử dụng C rất nhiều và tôi đã thực hiện một vài thao tác khử IOCCC cho sở thích cá nhân trước đây (lần cuối cùng tôi làm, chỉ vì lợi ích cá nhân, là công cụ raytracer tuyệt đẹp này ). Nếu bạn muốn hỏi làm thế nào nó hoạt động, tôi sẽ vui lòng bắt buộc;)
nneonneo

5
@ Рррррррррррр Chương trình đó cũng rất thông minh, vì nó không sử dụng từ khóa .
nneonneo

178
C .. tất cả sức mạnh của ngôn ngữ lắp ráp kết hợp với khả năng đọc của ngôn ngữ lắp ráp
wim

6
Để biết thêm chi tiết này, hãy xem Cốt truyện Obfuscated C và các bí ẩn khác, của Don Libes. Nó dạy các kỹ thuật C bằng cách phân tích các mục Cuộc thi C Obfuscated.
Chris N

102

Hãy định dạng này để dễ đọc hơn:

main(_){
  _^448&&main(-~_);
  putchar((--_%64) ? (32|-(~7[__TIME__-_/8%8])[">'txiZ^(~z?"-48]>>(";;;====~$::199")[_*2&8|_/64]/(_&2?1:8)%8&1):10);
}

Vì vậy, chạy nó không có đối số, _ (argc thông thường) là 1. main()sẽ gọi đệ quy chính nó, chuyển qua kết quả của -(~_)(bit bit âm KHÔNG phải của _), vì vậy thực sự nó sẽ đi được 450 lần truy cập (Chỉ với điều kiện là _^448 == 0).

Lấy đó, nó sẽ in 7 dòng rộng 64 ký tự (điều kiện ternary bên ngoài và 448/64 == 7). Vì vậy, hãy viết lại nó sạch hơn một chút:

main(int argc) {
  if (argc^448) main(-(~argc));
  if (argc % 64) {
    putchar((32|-(~7[__TIME__-argc/8%8])[">'txiZ^(~z?"-48]>>(";;;====~$::199")[argc*2&8|argc/64]/(argc&2?1:8)%8&1));
  } else putchar('\n');
}

Bây giờ, 32là số thập phân cho không gian ASCII. Nó hoặc in một khoảng trắng hoặc '!' (33 là '!', Do đó ' &1' ở cuối). Hãy tập trung vào blob ở giữa:

-(~(7[__TIME__-argc/8%8][">'txiZ^(~z?"-48]) >>
     (";;;====~$::199"[argc*2&8|argc/64]) / (argc&2?1:8) % 8

Như một poster khác đã nói, __TIME__là thời gian biên dịch cho chương trình và là một chuỗi, do đó, có một số số học chuỗi đang diễn ra, cũng như tận dụng một chỉ số mảng là hai chiều: a [b] giống như b [a] cho mảng ký tự.

7[__TIME__ - (argc/8)%8]

Điều này sẽ chọn một trong 8 ký tự đầu tiên __TIME__. Điều này sau đó được lập chỉ mục thành [">'txiZ^(~z?"-48](0-9 ký tự là 48-57 thập phân). Các ký tự trong chuỗi này phải được chọn cho các giá trị ASCII của chúng. Thao tác mã ASCII cùng ký tự này tiếp tục thông qua biểu thức, dẫn đến việc in một '' hoặc '!' tùy thuộc vào vị trí trong glyph của nhân vật.


49

Thêm vào các giải pháp khác, -~xbằng x+1~xtương đương với (0xffffffff-x). Đây là bằng (-1-x)trong bổ sung 2s, vì vậy -~x-(-1-x) = x+1.


5
Hấp dẫn. Tôi đã biết một thời gian rằng ~ x == -x - 1, nhưng tôi không biết lý do toán học đằng sau nó.
Tiếp

3
Ey, Cole, (-1-x) giống như (-x-1), bạn không cần phải "sửa" nó !!
Thomas Song

7
Lý do tương tự tại sao nếu ai đó là -1338, thì họ KHÔNG 1337.
Andrew Mao

4

Tôi đã làm xáo trộn các arithologists càng nhiều càng tốt và loại bỏ các reccursion

int pixelX, line, digit ;
for(line=6; line >= 0; line--){
  for (digit =0; digit<8; digit++){
    for(pixelX=7;pixelX > 0; pixelX--){ 
        putchar(' '| 1 + ">'txiZ^(~z?"["12:34:56"[digit]-'0'] >> 
          (";;;====~$::199"[pixel*2 & 8  | line] / (pixelX&2 ? 1 : 8) ) % 8 & 1);               
    }
  }
  putchar('\n');
}

Mở rộng thêm một chút:

int pixelX, line, digit, shift;
char shiftChar;
for(line=6; line >= 0; line--){
    for (digit =0; digit<8; digit++){
        for(pixelX=7;pixelX >= 0; pixelX--){ 
            shiftChar = ";;;====~$::199"[pixelX*2 & 8 | line];
            if (pixelX & 2)
                shift = shiftChar & 7;
            else
                shift = shiftChar >> 3;     
            putchar(' '| (">'txiZ^(~z?"["12:34:56"[digit]-'0'] + 1) >> shift & 1 );
        }

    }
    putchar('\n');
}
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.