Concours de code C obscurci 2006. Veuillez expliquer sykes2.c

975

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 se compile tel quel (testé sur gcc 4.6.3). Il imprime l'heure lors de la compilation. Sur mon système:

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

Source: sykes2 - Une horloge sur une seule ligne , suggère l'auteur sykes2

Quelques conseils: 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]
banal
la source
6
Débogage: Ajout printf("%d", _);au début des mainimpressions: pastebin.com/HHhXAYdJ
ringard
Entier, chaque variable non typée par défaut estint
drahnr
18
Avez-vous lu l'indice? ioccc.org/2006/sykes2/hint.text
nhahtdh
Si vous le lancez comme ça, il se bloque:./a.out $(seq 0 447)
SS Anne

Réponses:

1819

Désobscurcissons-le.

Indentation:

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

Présentation de variables pour démêler ce gâchis:

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 cela à -~i == i+1cause 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 que a[b]c'est la même choseb[a] et appliquez à -~ == 1+nouveau la modification:

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écursivité en boucle et se faufiler dans un peu plus de simplification:

// 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 sort une nouvelle ligne. Sinon, il utilise une paire de tableaux de données pour déterminer ce qu'il faut sortir et met soit le caractère 32 (un espace) soit le caractère 33 (a !). Le premier tableau ( ">'txiZ^(~z?") est un ensemble de 10 bitmaps décrivant l'apparence de chaque caractère, et le second tableau ( ";;;====~$::199") sélectionne le bit approprié à afficher dans le bitmap.

Le deuxième tableau

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

if((i & 2) == 0) shift /= 8; shift = shift % 8sélectionne soit le chiffre octal haut (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 table (sournois!).

Ceci est conçu après un affichage à sept segments, avec 7s comme blancs. Ainsi, les entrées du premier tableau doivent 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 48 à 57 et :une valeur ASCII 58. La sortie est de 64 caractères par ligne, ce qui laisse 8 caractères par caractère de __TIME__.

7 - i/8%8est donc l'indice de ce __TIME__qui est actuellement en cours de sortie (le 7-est nécessaire parce que nous itérons ivers le bas). Donc, tc'est le caractère d' __TIME__être sorti.

afinit par égaler ce qui suit en binaire, selon 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. Comme les caractères sont tous en ASCII 7 bits, le bit haut est toujours effacé. Ainsi, 7dans la table des segments, l'impression est toujours vide. Le deuxième tableau ressemble à ceci avec le 7s en blanc:

000055  
11  55  
11  55  
116655  
22  33  
22  33  
444433  

Ainsi, par exemple, 4est 01101010(bits 1, 3, 5 et 6 définis), qui s'imprime comme

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

Pour montrer que nous comprenons vraiment le code, ajustons un peu la sortie avec ce tableau:

  00  
11  55
11  55
  66  
22  33
22  33
  44

Ceci est codé comme "?;;?==? '::799\x07". À des fins artistiques, nous ajouterons 64 à quelques-uns des 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 n'est pas utilisé, nous pouvons donc en faire ce que nous voulons). Mettre notre nouvelle table dans le code d'origine:

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 le tableau qu'il a fait.

nneonneo
la source
2
@drahnr - Techniquement, c'est à la fois un *(déréférencement) et un +: P
detly
18
@ АртёмЦарионов: Environ 30 minutes, mais je suis revenu et l'ai assez bien édité. J'utilise beaucoup C, et j'ai déjà fait quelques désobfuscations IOCCC pour un intérêt personnel (le dernier que j'ai fait, juste pour un intérêt personnel, était ce magnifique raytracer ). Si vous voulez savoir comment ça marche, je serais heureux de vous obliger;)
nneonneo
5
@ АртёмЦарионов: environ une journée IIRC (compte également le temps passé à comprendre la géométrie du raytracer). Ce programme est également très intelligent, car il n'utilise aucun mot-clé .
nneonneo
178
C .. toute la puissance du langage d'assemblage combinée à la lisibilité du langage d'assemblage
wim
6
Pour en savoir plus dans cette veine, consultez «Obfuscated C et autres mystères», par Don Libes. Il enseigne les techniques C en analysant les entrées du concours C obscurci.
Chris N
102

Formater ceci pour une lecture plus facile:

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

Donc, en l'exécutant sans arguments, _ (argc conventionnellement) l'est 1. main()s'appellera récursivement lui-même, en passant le résultat de -(~_)(NOT négatif au niveau du bit de _), donc vraiment ça ira 448 récursions (Seulement condition où _^448 == 0).

En prenant cela, il imprimera 7 lignes larges de 64 caractères (la condition ternaire externe, 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, 32est 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__c'est le temps de compilation du programme, et c'est une chaîne, donc il y a de l'arithmétique de chaîne en cours, ainsi que de tirer parti d'un indice de tableau bidirectionnel: a [b] est identique à b [a] pour les tableaux de caractères.

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

Cela sélectionnera l'un des 8 premiers caractères de __TIME__. Il est ensuite indexé en [">'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. Cette même manipulation de code ASCII de caractère continue à travers l'expression, pour aboutir à l'impression d'un '' ou '!' en fonction de l'emplacement dans le glyphe du personnage.

chmeee
la source
49

Ajout aux autres solutions, -~xest égal à x+1car ~xest équivalent à (0xffffffff-x). C'est égal à (-1-x)en complément de 2s, il en -~xest de même -(-1-x) = x+1.

Thomas Song
la source
5
Intéressant. Je savais depuis un certain temps que ~ x == -x - 1, mais je ne connaissais pas le raisonnement mathématique derrière cela.
ApproachingDarknessFish
3
Ey, Cole, (-1-x) est le même que (-x-1), vous n'avez pas besoin de le "réparer" !!
Thomas Song
7
La même raison pour laquelle si quelqu'un a -1338, alors il n'est PAS 1337.
Andrew Mao
4

J'ai désobscurci autant que je le pouvais les arithmétiques modulo et j'ai supprimé

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

Élargir 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');
}
Lefteris E
la source