Obscurci C Code Contest 2006. S’il vous plaît expliquer sykes2.c

Comment fonctionne ce programme C?

main(_){_^448&&main(-~_);putchar(--_%64?32|-~7[__TIME__-_/8%8][">'txiZ^(~z?"-48]>>";;;====~$::199"[_*2&8|_/64]/(_&2?1:8)%8&1:10);} 

Il comstack tel quel (testé sur gcc 4.6.3 ). Il imprime l’heure lors de la compilation. Sur mon système:

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

Source: sykes2 – Une horloge dans une ligne , les indications de sykes2

Quelques astuces: Aucun avertissement de compilation par défaut. Compilé avec -Wall , les avertissements suivants sont émis:

 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] 

Désembrouillons-le.

En retrait:

 main(_) { _^448 && main(-~_); putchar(--_%64 ? 32 | -~7[__TIME__-_/8%8][">'txiZ^(~z?"-48] >> ";;;====~$::199"[_*2&8|_/64]/(_&2?1:8)%8&1 : 10); } 

Introduire des variables pour démêler ce désordre:

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

Notez que -~i == i+1 cause du complément à deux. Par conséquent, nous avons

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

Maintenant, notez qu’un a[b] est le même que b[a] , et appliquez à nouveau le changement -~ == 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)); } } 

Convertir la récursion en boucle et se faufiler un peu plus en simplifiant:

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

Cela génère un caractère par itération. Chaque 64ème caractère, il génère une nouvelle ligne. Sinon, il utilise une paire de tables de données pour déterminer ce qu’il faut sortir et met soit le caractère 32 (un espace) ou le caractère 33 (a ! ). La première table ( ">'txiZ^(~z?" ) Est un ensemble de 10 bitmaps décrivant l’apparence de chaque caractère et la seconde table ( ";;;====~$::199" ) sélectionne la bit approprié à afficher à partir du bitmap.

La deuxième table

Commençons par examiner la deuxième table, int shift = ";;;====~$::199"[(i*2&8) | (i/64)]; int shift = ";;;====~$::199"[(i*2&8) | (i/64)]; . i/64 est le numéro de ligne (6 à 0) et i*2&8 est 8 si i 4, 5, 6 ou 7 mod 8.

if((i & 2) == 0) shift /= 8; shift = shift % 8 if((i & 2) == 0) shift /= 8; shift = shift % 8 sélectionne soit le chiffre octal élevé (pour i%8 = 0,1,4,5), soit le chiffre octal bas (pour i%8 = 2,3,6,7) de la valeur du tableau. La table de décalage finit par ressembler à ceci:

 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 

ou sous forme de tableau

 00005577 11775577 11775577 11665577 22773377 22773377 44443377 

Notez que l’auteur a utilisé le terminateur nul pour les deux premières entrées de la table (sneaky!).

Ceci est conçu après un affichage à sept segments, avec 7 s en blanc. Les entrées du premier tableau doivent donc définir les segments qui s’allument.

Le premier tableau

__TIME__ est une macro spéciale définie par le préprocesseur. Il se développe en une constante de chaîne contenant l’heure à laquelle le préprocesseur a été exécuté, sous la forme "HH:MM:SS" . Notez qu’il contient exactement 8 caractères. Notez que 0-9 ont des valeurs ASCII de 48 à 57 et : a une valeur ASCII de 58. La sortie est de 64 caractères par ligne, ce qui laisse 8 caractères par caractère de __TIME__ .

7 - i/8%8 est donc l’indice de __TIME__ qui est actuellement en cours de production (le 7- est nécessaire car nous itérons i vers le bas). Donc, t est le caractère de __TIME__ cours de sortie.

a finit par égaler ce qui suit en binary, en fonction de l’entrée t :

 0 00111111 1 00101000 2 01110101 3 01111001 4 01101010 5 01011011 6 01011111 7 00101001 8 01111111 9 01111011 : 01000000 

Chaque numéro est un bitmap décrivant les segments qui sont allumés dans notre affichage à sept segments. Les caractères étant tous en ASCII 7 bits, le bit haut est toujours effacé. Ainsi, 7 dans le tableau des segments s’affiche toujours en blanc. Le deuxième tableau ressemble à ceci avec les 7 s comme des blancs:

 000055 11 55 11 55 116655 22 33 22 33 444433 

Ainsi, par exemple, 4 correspond à 01101010 (bits 1, 3, 5 et 6), qui imprime comme

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

Pour montrer que nous comprenons vraiment le code, ajustons un peu le résultat avec cette table:

  00 11 55 11 55 66 22 33 22 33 44 

Ceci est codé comme "?;;?==? '::799\x07" . Pour des raisons artistiques, nous appendons 64 à quelques caractères (puisque seuls les 6 bits les plus faibles sont utilisés, cela n’affectera pas la sortie); cela donne "?{{?}}?gg::799G" (notez que le 8ème caractère est inutilisé, nous pouvons donc le rendre tel que nous voulons). Mettre notre nouvelle table dans le code original:

 main(_){_^448&&main(-~_);putchar(--_%64?32|-~7[__TIME__-_/8%8][">'txiZ^(~z?"-48]>>"?{{?}}?gg::799G"[_*2&8|_/64]/(_&2?1:8)%8&1:10);} 

on a

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

comme nous nous y attendions. Ce n’est pas aussi solide que l’original, ce qui explique pourquoi l’auteur a choisi d’utiliser la table qu’il a faite.

Formulons cela pour faciliter la lecture:

 main(_){ _^448&&main(-~_); putchar((--_%64) ? (32|-(~7[__TIME__-_/8%8])[">'txiZ^(~z?"-48]>>(";;;====~$::199")[_*2&8|_/64]/(_&2?1:8)%8&1):10); } 

Donc, le lancer sans arguments, _ (argc conventionnellement) est 1 . main() se rappellera récursivement, en passant le résultat de -(~_) (bit négatif au lieu de _ ), donc vraiment 448 récursions (seule condition où _^448 == 0 ).

En prenant cela, il imprimera 7 lignes de 64 caractères (la condition externe ternaire et 448/64 == 7 ). Alors réécrivons-le un peu plus propre:

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

Maintenant, 32 est décimal pour l’espace ASCII. Il imprime un espace ou un ‘!’ (33 est ‘!’, D’où le ‘ &1 ‘ à la fin). Concentrons-nous sur le blob au milieu:

 -(~(7[__TIME__-argc/8%8][">'txiZ^(~z?"-48]) >> (";;;====~$::199"[argc*2&8|argc/64]) / (argc&2?1:8) % 8 

Comme l’a dit une autre affiche, __TIME__ est le temps de compilation du programme et est une chaîne. Il y a donc une certaine arithmétique de chaîne, tout en tirant parti d’un indice de tableau bidirectionnel: a [b] est identique à b [a ] pour les tableaux de caractères.

 7[__TIME__ - (argc/8)%8] 

Ceci sélectionnera l’un des 8 premiers caractères de __TIME__ . Celui-ci est ensuite indexé dans [">'txiZ^(~z?"-48] (0-9 caractères sont 48-57 décimaux). Les caractères de cette chaîne doivent avoir été choisis pour leurs valeurs ASCII. Ce même caractère de code ASCII la manipulation continue à travers l’expression, pour aboutir à l’impression soit d’un “ou” selon l’emplacement dans le glyphe du personnage.

En ajoutant aux autres solutions, -~x est égal à x+1 car ~x est équivalent à (0xffffffff-x) . Ceci est égal à (-1-x) en complément à 2s, donc -~x est -(-1-x) = x+1 .

J’ai désembrouillé autant que possible l’arithmétique modulo et enlevé la reccursion

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

En le développant un peu plus:

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