这个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]

当前回答

为了便于阅读,我们将其格式化:

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 of _)的结果,因此实际上它将进行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]

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

其他回答

让我们澄清一下。

缩进:

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 (a !)。第一个表(">'txiZ^(~z?")是一组10个位图,描述每个字符的外观,第二个表(";;;;====~$::199")从位图中选择要显示的适当位。

第二张表格

让我们开始通过检查第二个表,int转变 = ";;;====~$:: 199”(我* 2,8条)| (i / 64));。I /64是行号(6到0),I *2&8是8,如果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段显示之后设计的,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是空的:

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

我们得到了

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

不出我们所料。它看起来不像原来的那样坚固,这就解释了为什么作者选择使用他所做的表格。

为了便于阅读,我们将其格式化:

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 of _)的结果,因此实际上它将进行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]

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

加上其他解,-~x等于x+1,因为~x等价于(0xffffffff-x)。它等于2s补中(-1-x)所以-x = -(-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');
}