<- PW: MultiGnomeTerminal - Copertina - SL: Intro ->

PlutoWare


In-C-omprensibile è bello!

a cura di Mano


L'articolo...

L'IOCCC è ormai giunto alla sedicesima edizione... il termine per le iscrizioni è scaduto da poco, e i giudici stanno decidendo i vincitori per il 2001. Vediamo assieme cos'è questo concorso e perché è così interessante.


Indice


IOCCC: ma che è?

    Ai primi di dicembre del 2001 si sono ufficialmente chiuse le iscrizioni al sedicesimo Concorso Internazionale di Codice C Offuscato (International Obfuscated C Code Contest, IOCCC appunto). Questa è probabilmente una delle iniziative più divertenti (senza comunque perdere i connotati di serietà) che riguardano il mondo della programmazione C, solitamente considerato serio e arido. Esso nasce dall'ispirazione di Landon Curt Noll, Simon Cooper, Peter Seebach e Leonid A. Broukhis che, raccontano, vedendo i sorgenti della Shell Bourne e del comando finger distribuiti nella BSD4.2, si posero una domanda: se quelli erano i risultati di ciò che la gente chiamava un ragionevole esercizio di programmazione, cosa si poteva ottenere mettendosi d'impegno per produrre codice che fosse scritto il peggio possibile?

    Sulla falsariga di un concorso che proponeva di scrivere la peggiore frase iniziale di un racconto (a sua volta ispirato da uno scritto che cominciava col classicissimo "Era una notte buia e tempestosa"), si arrivò così, nel 1984, alla prima edizione dell'IOCCC. Questa competizione negli intenti avrebbe dovuto instillare negli "spettatori" il disgusto per i cattivi stili di programmazione, e la consapevolezza di quanto un programma scritto male perda in utilità. In effetti, questo risultato è stato negli anni ampiamente raggiunto, col grosso effetto collaterale di creare anche una nuova entità, la "programmazione satirica". Infatti, come la satira è in fondo un'attività dell'intelletto volta a evidenziare e screditare un cattivo costume, così i programmi che sono stati premiati nelle più diverse categorie in questi anni rappresentano tutt'altro che degli orrori: tutti sono estremamente interessanti da analizzare, e sorprendono per i diversi modi (intelligentissimi) con cui appaiono "brutti e incomprensibili": insomma, per mettere in luce i possibili difetti nello stile di programmazione (che son ben diversi dai bug!), dimostrano al contempo una grandissima cura nel... fare del proprio peggio un'arte.

    Come ogni competizione, anche questa ha dei fini e delle regole. Si propone infatti di:

    Le regole sono poche, in effetti:     Oltre ad aver imposto queste regole, i giudici invitano ad abusarne, in pratica a portarle al limite; è interessantissimo vedere come i vari programmatori (tra cui spiccano nomi decisamente noti, come Larry Wall) hanno spaziato nell'uso e abuso di esse! Ci sono sorgenti che generano altri sorgenti che generano altri sorgenti e così via; programmi che producono l'output che si propongono in fase di compilazione; programmi il cui sorgente funziona da sorgente C, da makefile e da script Bash tutto assieme, e così via... È così d'obbligo creare tutta una serie di categorie di "migliori programmi offuscati", una sola sarebbe stata davvero riduttiva... :-)

    Risultato di tutto questo è che in quindici edizioni si è prodotta una collezione decisamente corposa di programmi bizzarri, strani e interessantissimi per chiunque sia appassionato di programmazione, e in particolare del magico mondo del C, il linguaggio "più a basso livello tra i linguaggi ad alto livello". Essi costituiscono un esempio meraviglioso di come non servano centinaia di megabyte per fare un programma completo (ok, ok, sono comunque programmi semplici... ma un foglio elettronico in X con tanto di grafici in 2032 byte come lo valutate? :-), e soprattutto sono un po' la "Settimana Enigmistica" del programmatore, una divertentissima palestra per testare le proprie abilità di comprensione!

    In attesa di conoscere i vincitori del 2001, presentiamo degli esempi scelti tra i vincitori degli anni scorsi; nel prossimo numero ne vedremo altri, e daremo alcuni consigli per riuscire a penetrare nel codice... per ora, provateci da soli, o semplicemente godetevi la sua spettacolare incomprensibilità!

natori.c: cominciamo con le cose semplici... anzi, mica troppo...

    Questo programma di Natori Shin (natori@is.s.u-tokyo.ac.jp) è stato premiato nell'edizione del 2000 nella sezione "best small program" (miglior piccolo programma): in effetti, è proprio piccolo, come del resto molti altri premiati nell'IOCCC... ma fa qualcosa che non ci si aspetterebbe da una riga di codice... di nuovo, come tutti gli altri programmi premiati! ;-) Vediamo il listato:

  #include <stdio.h>
  #include <math.h>
  double l;main(_,o,O){return putchar((_--+22&&_+44&&main(_,-43,_),_&&o)?(main(-43,++o,O),((l=(o+21)/sqrt(3-O*22-O*O),l*l<4&&(fabs(((time(0)-607728)%2551443)/405859.-4.7+acos(l/2))<1.57))[" #"])):10);}

    Copiamolo ora in un file, tipo natori.c, compiliamolo e lanciamolo con i comandi:

  cc natori.c -lm -o natori
  ./natori

    otterremo qualcosa di simile a:

                       ##                  
                         ######            
                          #########        
                           ##########      
                           ############    
                            ############   
                            #############  
                            ############## 
                             ##############
                             ##############
                             ##############
                             ##############
                             ##############
                             ##############
                             ##############
                            ############## 
                            #############  
                            ############   
                           ############    
                           ##########      
                          #########        
                         ######            
                       ##                  

    Ebbene... questa non è altro che la corrente fase lunare!! Ma come fa? Provate a dare un'occhiata al programma: è sicuramente molto ben offuscato, ma la sua interpretazione non è proprio impossibile... è solo molto difficile! ;-) Si basa su una chiamata ricorsiva a main();, notate che "_" è una variabile, che 2551443 sono i secondi di un ciclo lunare, e 607728 indica (sempre in secondi) l'"offset" del tempo UNIX 0 rispetto al ciclo lunare. Per il resto... beh, buon divertimento! :-D


anderson.c: offuscamento e immaginazione

    Questo programma di Anderson Glyn (glyn@mataharistudios.com) è un bell'esempio di offuscamento, ma ha anche qualcosa in più :

  #include <stdio.h>

  char
  *T="IeJKLMaYQCE]jbZRskc[SldU^V\\X\\|/_<[<:90!\"$434-./2>]s",
  K[3][1000],*F,x,A,*M[2],*J,r[4],*g,N,Y,*Q,W,*k,q,D;X(){r  [r
  [r[3]=M[1-(x&1)][*r=W,1],2]=*Q+2,1]=x+1+Y,*g++=((((x&     7)
  -1)>>1)-1)?*r:r[x>>3],(++x<*r)&&X();}E(){A||X(x=0,g       =J
  ),x=7&(*T>>A*3),J[(x[F]-W-x)^A*7]=Q[x&3]^A*(*M)[2         +(
  x&1)],g=J+((x[k]-W)^A*7)-A,g[1]=(*M)[*g=M[T+=A            ,1
  ][x&1],x&1],(A^=1)&&(E(),J+=W);}l(){E(--q&&l              ()
  );}B(){*J&&B((D=*J,Q[2]<D&&D<k[1]&&(*g++=1                ),
  !(D-W&&D-9&&D-10&&D-13)&&(!*r&&(*g++=0)                   ,*
  r=1)||64<D&&D<91&&(*r=0,*g++=D-63)||D                     >=
  97&&D<123&&(*r=0,*g++=D-95)||!(D-k[                       3]
  )&&(*r=0,*g++=12)||D>k[3]&&D<=k[                          1]
  -1&&(*r=0,*g++=D-47),J++));}j(                            ){
  putchar(A);}b(){(j(A=(*K)[D*                              W+
  r[2]*Y+x]),++x<Y)&&b();}t                                 ()
  {(j((b(D=q[g],x=0),A=W)                                   ),
  ++q<(*(r+1)<Y?*(r+1):                                     Y)
  )&&t();}R(){(A=(t(                                        q=
  0),'\n'),j(),++r                                          [2
  ]<N)&&R();}O()                                            {(
  j((r[2]=0,R(                                              ))
  ),r[1]-=q)                                                &&
  O(g-=-q)                                                  ;}
  C(){(                                                     J=
  gets                                                      (K
  [1]))&&C((B(g=K[2]),*r=!(!*r&&(*g++=0)),(*r)[r]=g-K[2],g=K[2
  ],r[
  1]&&
  O())
  );;}
  main
  (){C
  ((l(
  (J=(
  A=0)
  [K],
  A[M]
  =(F=
  (k=(
  M[!A
  ]=(Q
  =T+(
  q=(Y
  =(W=
  32)-
  (N=4
  ))))
  +N)+
  2)+7
  )+7)
  ),Y=
  N<<(
  *r=!
  -A))
  );;}

    A parte la forma (di cui diremo dopo), notate come sia ben offuscato: anche questo è a prima vista incomprensibile, ma c'è di più: noterete che, a parte il char iniziale (una variabile gliela possiamo pure concedere! ;) manca del tutto di parole riservate in C! Il programma è tutto basato sulle regole di precedenza degli operatori, sulla forma breve di valutazione di espressioni ("short circuit evaluation", la forma cond ? expr1 : expr2) e sulla ricorsione. Inoltre le funzioni non hanno return type, né ritornano effettivamente nulla; questo potrebbe generare warning in molti compilatori, ma nel C di Kerningham e Ritchie (e anche nell'ANSI C) non è vietato. Ma cosa fa? Come sempre, copiamolo in un file, compiliamolo e avviamolo:

  cc anderson.c -o anderson
  echo prova|./anderson

    il risultato sarà:

    []              <>       []
  _()|     _()_     _\)      |()       ()       ()    
[] ^^    [] ^^ [] [] ^^       ^^\     /^^|     |^^|   
   ][       ][       ][       ][<>   <>][]     [][]

    Eccovi una ragazza (presumibilmente... ;-) che rappresenta la stringa "prova" mediante il codice delle bandierine, con tanto di segnalazione di chiusura! Questo spiega, assieme, la forma particolare del sorgente e il premio assegnato a questo programma, incoronato nel 2000 per "Best Use of Flags", con un gioco di parole purtroppo intraducibile. ;-)

Molti programmi premiati nell'IOCCC fanno del layout un punto di forza: infatti il C, come molti altri linguaggi, ignora gli spazi e le tabulazioni, così un programma può essere formattato letteralmente come si vuole... offuscamento come forma d'arte ASCII?

    È evidente che il pregio di questo programma non è tanto nella futilità dello scopo raggiunto, quanto nella sottigliezza e finezza dell'offuscamento adottato, e nell'intelligente adattamento dell'algoritmo (per non parlare delle stringhe che compongono la ragazza... le vedete?) ad una struttura ricorsiva e priva di parole chiave... senza l'uso di un singolo #define! Ecco quindi un ottimo esempio di "programmazione satirica", nel perfetto stile di IOCCC.


thadgavin.c: spazio allo spettacolo!

    Terminiamo questa prima carrellata con un programma di Thaddaeus Frogley (thad@creaturelabs.com) e Gavin Buttimore (gavin.buttimore@creaturelabs.com) che nel 2000 ha vinto il titolo di "Most Portable Output": questo perché è compilabile sotto DOS, Windows, Mac e lavora sia in modalità grafica (usando le SDL) che in modalità ASCII, usando le curses. Questo però è un po' ingeneroso verso un programma che in 2876 byte fa... beh, vediamolo assieme:

                        int
                     X=320      ,Y=200,
                   n=0,m,     x,y,   j=1024;
                 double     T=44.0     /7,P[
                333333     ],C[5]       ={ 0,3,
                0,0,8}      ,p=1,         B=11.0
                 /630,      f=0,r   =     3,g
                  =7,b        =13,*q=P,   D,*J;
                  unsigned               char
                    U[66666],*v=U,*h,l[5555]
                         ,c=0,*e,*a,*z;

                      #include <math.h>
                  #define R1(t)   t=(int)(t\
                *123456789           )%j; t/=j;
               #define                 Rl(C,t)\
               n++[C]         =         t*n/12;
                #define      RI(C)     B=-B; R1\
                (r)R1(g     )R1(b     )for(n\
                 =0; n<j; ){ Rl(C   ,r)Rl\
                     (C,g)Rl(C    ,b)++n; }



         #ifdef __DJGPP__
           #include <sys/movedata.h>
                 #include <dpmi.h>
                   #include <pc.h>
     #define          Q(u,v)         u##portb(0x3##v
       #define          W        ; Q(out,C9),*h++/4)
            void       F(int i){  __dpmi_regs r
             ; if(i){ for(; i>=0; i-=8)while(
                       ~Q(in,DA) 
                    )&8^i); for(m=0,z
                =h+j; h    <z; m     ++){ Q(
           out,C8),m     )W W W; ++h; } dosmemput
     (v,X*Y,0xA0000   ); } else{       r.x.ax=
   0x13;            __dpmi_int(    0x10,&r); } }
                     #elif defined(SDL)
                #include "SDL/SDL.h"
            SDL_Surface    *s; void
           F(int i){ if   (i){ SDL_SetColors(
       s,h,0,256);         SDL_UpdateRect
       (s,0,0,0,        0); } else {  SDL_Init( 
         SDL_INIT_VIDEO); s=SDL_SetVideoMode
         (X,Y,8,0);       v=s->pixels; } }
                    #else
                 #include "curses.h"
                void F(i){ if(i){ for(y=0;
            y<X*Y                   ; y++)
           { move  (y/X,y%X);        addch
          ((*(v   +y)/     32)      [" ."
          ",:+"   "=@#"   ]); } ;  refresh
          (); }     else{          initscr
          (),x=     COLS&~1,X=x<X?x:X,y=
           LINES      &~1,Y=y<Y?y:Y; } }
            #endif

  main()
  {
      F(0);

      for (x=-X/2,y=-Y/2;y<Y/2;++x>=X/2?x=-X/2,y++:4)
                  {*q++ = sqrt(x*x+y*y);

      *q++ = atan2(x,y);

      }for (;n<j*2;l[n++]=0);
          for(;;)
          {
              a=l;z=l+j;e=l+j*2;
              if ((p+=B)>1){p=2-p;RI(l+j)}
                          else if (p<0){p=-p;RI(l)}

              while(a<l+j) D=p**a+++(1-p)**z++,*e++=D;
              h=l+j*2;

              for (J=P,z=v; z<v+X*Y;){
                  D = *J++;
                  *z++=fabs(sin((*J+++C[1])*1.5+D*C[0]+C[2]*sin(C[3]+D/C[4]))*255);
              }F(8);

              C[2]+=B; f+=T/360; C[3]+=f;

              if (f>T)
                  {C[1] += (f-T)/8;

              if (f>T*2)
                  C[0]=sin(f)+sin(f*2)/2;
          }
      }
  }

    Questo programma è compatto, deliziosamente incomprensibile (anche se l'offuscamento è meno sofisticato che in altri programmi... del resto ricordate? È portatile, per cui non potrebbe "giocare troppo sporco") e ha un layout molto carino... per informazione, secondo gli autori i primi due blocchi rappresentano il vostro cervello prima e dopo aver usato il programma! Comunque, salviamo in un file e compiliamo:

  #con le librerie SDL (consigliato!)
  cc thadgavin.c -ansi -O2 -lm -lSDL -DSDL -o thadgavin
  #se non si hanno, con le curses in modalità ASCII
  cc thadgavin.c -ansi -O2 -lm -lcurses -o thadgavin

  ./thadgavin

    ...buona visione! :o)

Una parola ancora

    Se siete curiosi, la miglior cosa che possiate fare è una visita al sito di IOCCC (http://www.ioccc.org): qui troverete una vera miniera di programmi (ce ne sono altri 160!!!), ordinati e commentati, per soddisfare tutte le vostre curiosità, in attesa del prossimo articolo e soprattutto dei vincitori del 2001!



A cura di

Germano "Mano" Rizzo (mano@pluto.linux.it), studente in Ingegneria Elettronica, è programmatore Java e PHP presso una ditta di software web. Si interessa di Linux da parecchi anni, spaziando in svariati campi, in particolare la programmazione C e gli aspetti legati al kernel.


<- PW: MultiGnomeTerminal - Copertina - SL: Intro ->