Конкурс C-коду з прихованим кодом 2006. Будь ласка, поясніть sykes2.c


975

Як працює ця програма C?

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

Вона компілюється як є (перевірена на gcc 4.6.3). Він друкує час, коли компілюється. У моїй системі:

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

Джерело: sykes2 - Годинник в одному рядку , підказки автора sykes2

Деякі підказки: Немає компіляційних попереджень за замовчуванням. У компіляції -Wallвидаються такі попередження:

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
Налагодження: додавання printf("%d", _);до початку mainвідбитків: pastebin.com/HHhXAYdJ
банально

Ціле число, кожна типова змінна значення за замовчуваннямint
drahnr

18
Ви читали підказку? ioccc.org/2006/sykes2/hint.text
nhahtdh


Якщо ви запускаєте його так, він виходить з ладу:./a.out $(seq 0 447)
SS Anne

Відповіді:


1819

Давайте знешкоджуємо це.

Відступ:

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

Представляємо змінні для розв’язування цього безладу:

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
    }
}

Зауважте, що -~i == i+1через двійки-доповнення. Тому маємо

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));
    }
}

Тепер зверніть увагу, що a[b]це те самеb[a] , і застосуйте -~ == 1+зміни знову:

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));
    }
}

Перетворення рекурсії в цикл і прокрадання в трохи більшому спрощенні:

// 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));
        }
    }
}

Це виводить один символ за ітерацію. Кожен 64-й символ виводить новий рядок. В іншому випадку він використовує пару таблиць даних, щоб визначити, що виводити, і ставить або символ 32 (пробіл), або символ 33 (а !). Перша таблиця ( ">'txiZ^(~z?") - це набір 10 растрових зображень, що описують зовнішній вигляд кожного символу, а друга таблиця ( ";;;====~$::199") вибирає відповідний біт для відображення з растрової карти.

Друга таблиця

Давайте почнемо з розгляду другий таблиці int shift = ";;;====~$::199"[(i*2&8) | (i/64)];. i/64- номер рядка (6 до 0) і i*2&8дорівнює 8 iff i- 4, 5, 6 або 7 mod 8.

if((i & 2) == 0) shift /= 8; shift = shift % 8вибирає або високу восьмеричну цифру (для i%8= 0,1,4,5), або низьку восьмеричну цифру (для i%8= 2,3,6,7) табличного значення. Таблиця зрушень виглядає так:

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

або в табличній формі

00005577
11775577
11775577
11665577
22773377
22773377
44443377

Зауважте, що автор використав нульовий термінатор для перших двох записів таблиці (підлий!).

Це розроблено після семисегментного дисплея з 7пробілами s. Отже, записи в першій таблиці повинні визначати сегменти, що засвічуються.

Перша таблиця

__TIME__є спеціальним макросом, визначеним препроцесором. Він розширюється до рядкової константи, що містить час, в який запускався препроцесор, у вигляді "HH:MM:SS". Зауважте, що він містить рівно 8 символів. Зауважте, що 0-9 мають значення ASCII від 48 до 57 та :мають значення ASCII 58. Вихід становить 64 символи на рядок, так що залишається 8 символів на символ __TIME__.

7 - i/8%8Таким чином, індекс того, __TIME__що зараз виводиться ( 7-необхідний, тому що ми повторюємо iвниз). Отже, tхарактер __TIME__виведення.

aв кінцевому підсумку дорівнює наступному у двійковій формі, залежно від введення t:

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

Кожне число - це растрове зображення, що описує сегменти, які висвітлюються на нашому семисегментному дисплеї. Оскільки символи всі 7-бітні ASCII, високий біт завжди очищається. Таким чином, 7у таблиці сегментів завжди друкується як порожнє. Друга таблиця виглядає так з 7пробілами:

000055  
11  55  
11  55  
116655  
22  33  
22  33  
444433  

Так, наприклад, 4є 01101010(біти 1, 3, 5 і 6 безліч), який друкує як

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

Щоб показати, що ми дійсно розуміємо код, давайте трохи відрегулюємо висновок за допомогою цієї таблиці:

  00  
11  55
11  55
  66  
22  33
22  33
  44

Це закодовано як "?;;?==? '::799\x07". Для мистецьких цілей ми додамо 64 до кількох символів (оскільки використовуються лише низькі 6 біт, це не вплине на вихід); це дає "?{{?}}?gg::799G"(зауважте, що 8-й символ не використовується, тому ми можемо зробити його все, що завгодно). Введення нової таблиці у вихідний код:

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

ми отримуємо

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

так, як ми очікували. Це не так солідний вигляд, як оригінал, що пояснює, чому автор вирішив використовувати таблицю, яку він зробив.


2
@drahnr - З технічної точки зору це як *(відвід), так і +: P
невдало

18
@ АртёмЦарионов: Близько 30 хвилин, але я повертався і неабияк редагував це. Я багато використовую C, і раніше я зробив декілька дефуфузацій МОКЦ для особистого інтересу (останній, який я зробив, лише для особистого інтересу, був цей прекрасний лучник ). Якщо ви хочете запитати, як це працює, я був би радий зобов'язатись;)
nneonneo

5
@ АртёмЦарионов: приблизно в день IIRC (також рахує час, витрачений на розуміння геометрії променевої стрічки). Ця програма також дуже розумна, оскільки не використовує ключових слів .
nneonneo

178
C .. вся сила асемблерної мови поєднується з читабельністю асемблерської мови
wim

6
Докладніше про це можна прочитати у "Заплутаному С та інших таємницях" дона Лібеса. Він викладає методики C, аналізуючи Захоплені записи C Конкурсу.
Кріс Н

102

Форматуємо це для легшого читання:

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

Отже, запускаючи його без аргументів, _ (argc умовно) є 1. main()буде рекурсивно викликати себе, передаючи результат -(~_)(негативний побіжно НЕ _), так що дійсно він піде 448 рекурсій (Тільки умова, де _^448 == 0).

Враховуючи це, він надрукує 7 широких ліній на 64 символи (зовнішній потрійний стан та 448/64 == 7). Тож давайте перепишемо його трохи чистіше:

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');
}

Тепер 32є десятковим для простору ASCII. Він або друкує пробіл, або "!" (33 - "!", Звідси " &1" в кінці). Давайте зосередимось на краплі посередині:

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

Як сказав інший плакат, __TIME__це час компіляції для програми і це рядок, тому відбувається деяка арифметика рядка, а також скористатися перевагами підрядного масиву, який є двонаправленим: a [b] - це те саме, що b [a] для масивів символів.

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

Це дозволить вибрати один з перших 8 символів у __TIME__. Потім він індексується в [">'txiZ^(~z?"-48](0-9 символів - 48-57 десяткових). Символи в цьому рядку повинні бути обрані для їх значень ASCII. Ця ж символьна маніпуляція кодом ASCII триває через вираз, щоб призвести до друку або '', або '!' залежно від місця розташування всередині гліфа персонажа.


49

Додавання до інших рішень -~xдорівнює x+1тому ~x, що еквівалентно (0xffffffff-x). Це дорівнює (-1-x)в 2s доповнення, так -~xє -(-1-x) = x+1.


5
Цікаво. Я деякий час знав, що ~ x == -x - 1, але я не знав математичних міркувань за цим.
Наближається

3
Ей, Коул, (-1-х) - це те саме, що (-x-1), не потрібно "виправляти" це !!
Thomas Song

7
Ця ж причина, чому якщо комусь -1338, то вони НЕ 1337.
Ендрю Мао

4

Я знеструмив модуль арифметики настільки, наскільки міг, і зняв рекурсію

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');
}

Розгорнувши його трохи більше:

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');
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.