最爱小鸭吧 关注:22贴子:2,422
  • 4回复贴,共1

IOCCC获奖作品赏析

只看楼主收藏回复

本帖主要赏析一些IOCCC的获奖作品。


IP属地:浙江来自Android客户端1楼2023-09-01 17:07回复
    This floor is reserved.


    IP属地:浙江来自Android客户端2楼2023-09-01 17:08
    回复
      anonymous(1984, Dishonorable mention)
      spoilers: prints hello world, where read is write
      source code:
      int i;main(){for(;i["]<i;++i){--i;}"];read('-'-'-',i+++"hell\
      o, world!\n",'/'/'/'));}read(j,i,p){write(j/p+p,i---j,i/i);}
      formatted code:

      赏析:
      先看两个函数头。我们都知道函数未指定返回值类型时默认返回int类型。read的参数没有注明类型,因为这是一种旧式的函数定义方法,具体细节这里不提,读者感兴趣可自行了解。结果就是j, i, p默认为int类型。
      再看main里的代码。for条件表达式的写法我们都很熟悉了,就是判断s[i]是否为\0。后面的read并非是读取函数,因为我们自己定义了read函数,它实现的是write的功能。
      我们化简一下read,写成:read(0, i++ + "hello, world!\n", 1);
      再看read函数的细节。从上面我们已经知道了j=0, p=1, 化简一下:write(1, i--, 1);
      write函数的用法大家都很熟悉,标准输出的文件描述符为1也是众所周知的。
      整个函数的作用就是打印i指向的那一个字符。
      我们再回头看main。通过for循环,用write一个字符一个字符地把hello world打印出来。注意前后两个字符串的长度是一样的,所以hello world打印完后,for循环也恰好结束。
      整个程序只有短短两行,理解起来也没有什么难度。


      IP属地:浙江3楼2023-09-01 17:11
      回复
        laman(1984, Third place)
        spoilers: prints spiralling numbers, laid out in columns
        source code:
        a[900];b;c;d=1;e=1;f;g;h;O;main(k,
        l)char**l;{g=atoi(*++l);for(k=
        0;k*k<g;b=k++>>1);for(h=0;h*h<=
        g;++h);--h;c=((h+=g>h*(h+1))-1)>>1;
        while(d<=g){++O;for(f=0;f<O&&d<=g
        ;++f)a[b<<5|c]=d++,b+=e;for(f=0;f<O
        &&d<=g;++f)a[b<<5|c]=d++,c+=e;e= -e
        ;}for(c=0;c<h;++c){for(b=0;b<k;++
        b){if(b<k/2)a[b<<5|c]^=a[(k-(b+1))
        <<5|c]^=a[b<<5|c]^=a[(k-(b+1))<<5|c]
        ;printf(a[b<<5|c]?"%-4d":" ",a[b<<5
        |c]);}putchar('\n');}}/*MikeLaman*/
        (贴吧无法输入tab,请见: https://www.ioccc.org/1984/laman/laman.c
        formatted code:

        sample:
        shell:
        ./laman 16
        output:
        10 9 8 7
        11 2 1 6
        12 3 4 5
        13 14 15 16
        赏析:
        第6行为g = atoi(argv[1]),即命令行输入的第一个参数被作为打印的数字总数。
        第7行计算总列数k和1所在的列b。k = ceil(sqrt(g)), b = (k - 1) / 2。
        当列数为偶数时,1应当出现在中间偏右的列,但我们先将1放在偏左的列。理由在随后会说。
        第8~10行计算总行数h和1所在的行c。第8~9行先算得h = floor(sqrt(g))。考虑g不是完全平方数,若g > h * (h + 1),说明行列需要相等,否则行数比列数少1。第10行对h进行修正,同时算得c = (h - 1) / 2。
        第11~18行做出map。d表示当前的数字,O表示一次循环需要写几个数字,e表示逆序与否。一次while写一行一列。用b << 5 | c来作为简单的hash值,这样我们就建立了一个无冲突的映射。
        这里又产生一个问题,写入map的值与打印的位置对不上,列刚好相反了。我们将它与上文遗留的问题(为什么1要放在偏左的列)结合起来,答案将在后文揭晓。
        第19~26行我们将其打印出来。第22行是很经典的三异或交换。但是,需要强调的是,这是个未定义行为,因为“若对一个标量对象的副效应与另一个对同一标量对象的副效应相对无顺序,则行为未定义”。
        我们将证明在三个副作用以符合直觉的顺序依次发生时,三个^=能够实现两个相异对象的值交换。
        我们分别用a和b来表示两个对象,即a ^= b ^= a ^= b;
        t = a ^ b
        b' = b ^ t = b ^ a ^ b = a
        a' = t ^ b' = a ^ b ^ a = b
        Q.E.D.
        看到这个if和交换,上文的问题就可以解决了。我们反着写入列,又在打印之前将其反回来。
        其余的部分都很容易理解,这里不再赘述。


        IP属地:浙江4楼2023-09-01 21:03
        回复
          applin(1985, Best one liner)
          spoilers: one-liner to print hello world, with execlp
          source code:
          main(v,c)char**c;{for(v[c++]="Hello, world!\n)";(!!c)[*c]&&(v--||--c&&execlp(*c,*c,c[!!c]+!!c,!c));**c=!c)write(!!*c,*c,!!**c);}
          formatted code:

          赏析:
          for的初始化表达式将"Hello, world!\n"放到c末尾,注意这个行为是未定义的,可能导致程序崩溃。然后c指向了argv[1],也就是Hello world字符串。
          再看条件表达式。先判断是否到达字符串的末尾。因为判断的是c[0][1],所以我们不会打印出最后的')'。
          v--,因为刚开始v=1,所以第一次我们直接write;第二次v--为假,执行后面的表达式。
          --c将c重新置为argv,然后调用execlp来fork自己。化简一下:execlp(*c, *c, c[1] + 1, NULL);
          我们将Hello world字符串+1再传入,这样每次打印第一个字符,就能将整个字符串打印出来了。
          **c = !c用来将我们打印过的那个字符置\0,可有可无。
          化简一下for循环体里的语句:write(1, *c, 1);
          也就是打印字符串的第一个字符。


          IP属地:浙江5楼2023-09-03 19:24
          回复