首页 文章

混淆的C代码竞赛2006.请解释sykes2.c

提问于
浏览
932

这个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 - A clock in one linesykes2 author hints

一些提示:默认情况下没有编译警告 . 使用 -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 回答

  • 1779

    让我们去混淆它 .

    缩进:

    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] is the same as 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
    

    请注意,作者使用null终止符作为前两个表条目(偷偷摸摸!) .

    这是在七段显示后设计的, 7 为空白 . 因此,第一个表中的条目必须定义亮起的段 .

    第一张表

    TIME是预处理器定义的特殊宏 . 它扩展为包含预处理器运行时间的字符串常量,格式为 "HH:MM:SS" . 注意它包含正好8个字符 . 请注意,0-9的ASCII值为48到57, : 的ASCII值为58.每行输出64个字符,因此每个字符的 __TIME__ 留下8个字符 .

    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 s为空白:

    000055  
    11  55  
    11  55  
    116655  
    22  33  
    22  33  
    444433
    

    因此,例如, 401101010 (位1,3,5和6集),其打印为

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

    为了表明我们真正理解代码,让我们用这个表调整输出:

    00  
    11  55
    11  55
      66  
    22  33
    22  33
      44
    

    这被编码为 "?;;?==? '::799\x07" . 出于艺术目的,我们会影响输出);这给了 "?{{?}}?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);}
    

    我们得到

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

    就像我们预期的那样 . 它不像原版一样坚固,这就解释了为什么作者选择使用他所做的表格 .

  • 3

    我们将其格式化以便于阅读:

    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() 将递归调用自身,传递 -(~_) 的结果( _ 的负按位NOT),所以它真的会去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 ' at the end). Let' s专注于中间的blob:

    -(~(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]
    

    这将选择 __TIME__ 中前8个字符之一 . 然后将其索引到 [">'txiZ^(~z?"-48] (0-9个字符为十进制48-57) . 必须为其ASCII值选择此字符串中的字符 . 这个相同的字符ASCII代码操作继续通过表达式,导致打印' '或'!',具体取决于字符字形内的位置 .

  • 46

    添加到其他解决方案, -~x 等于 x+1 ,因为 ~x 等同于 (0xffffffff-x) . 这相当于2s补码中的 (-1-x) ,所以 -~x-(-1-x) = x+1 .

  • 98

    我尽可能地模糊了模数算术并删除了递归

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

相关问题