Запутанный C Code Contest 2006. Пожалуйста, объясните sykes2.с

как работает эта программа 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]

4 ответов


давайте де-запутать его.

отступы:

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

обратите внимание, что автор использовал нуль-Терминатор для первых двух записей таблицы (sneaky!).

это после семи-сегментный дисплей, с 7S как бланки. Таким образом, записи в первой таблице должны определять сегменты, которые загораются.

первая таблица

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

индекс __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 is 01101010 (набор битов 1, 3, 5, и 6), который печатает as

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

чтобы показать, что мы действительно понимаем код, давайте немного скорректируем вывод с помощью этой таблицы:

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

мы

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

как мы и ожидали. Это не так солидно, как оригинал, что объясняет, почему автор решил использовать таблицу, которую он сделал.


давайте отформатируем это для облегчения чтения:

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 код манипуляции продолжается через выражение, чтобы привести к печати либо "или"!- в зависимости от расположения символа.


добавление к другим решениям,-~x равна x+1, потому что ~x эквивалентно (0xffffffff-x). Это равно (-1-x) в 2S дополняют, так что -~x is -(-1-x) = x+1.


Я де-запутал арифметику по модулю столько, сколько мог, и удалил рекурсию

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