Question Obfuscated C Code Contest 2006. Veuillez 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 compile tel quel (testé sur gcc 4.6.3). Il imprime l'heure lorsqu'il est compilé. Sur mon système:

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

La source: sykes2 - Une horloge en une ligne, sykes2 conseils de l'auteur

Quelques conseils: Pas d'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]

917
2018-03-13 18:22


origine


Réponses:


Débranchons-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);
}

Introduire des 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 que -~i == i+1 à cause de deux-complément. 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] est le même que b[a]et appliquez le -~ == 1+ changer encore:

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. Dans le cas contraire, il utilise une paire de tables de données pour déterminer ce qu'il doit sortir et place le caractère 32 (un espace) ou le caractère 33 (un !). La première table (">'txiZ^(~z?") est un ensemble de 10 bitmaps décrivant l'apparence de chaque caractère, et la deuxième table (";;;====~$::199") sélectionne le 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)];. i/64 est le numéro de ligne (6 à 0) et i*2&8 est de 8 ss i est 4, 5, 6 ou 7 mod 8.

if((i & 2) == 0) shift /= 8; shift = shift % 8 sélectionne soit le chiffre octal haut (pour i%8 = 0,1,4,5) ou le chiffre bas octal (pour i%8 = 2,3,6,7) de la valeur de la table. 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 null pour les deux premières entrées de la table (sournoise!).

Ceci est conçu après un affichage à sept segments, avec 7s comme des blancs. Ainsi, les entrées de la première table doivent définir les segments qui s'allument.

La première table

__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". Observez qu'il contient exactement 8 caractères. Notez que 0-9 ont des valeurs ASCII 48 à 57 et : a 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%8 est donc l'indice de __TIME__ qui est actuellement en cours de production (le 7- est nécessaire parce que nous itérons i vers le bas). Alors, t est le personnage de __TIME__ étant sortie.

a finit par égaler le suivant en binaire, 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 nombre est un bitmap décrire les segments qui sont éclairés dans notre affichage à sept segments. Comme les caractères sont tous ASCII 7 bits, le bit haut est toujours effacé. Ainsi, 7 dans la table des segments, imprime toujours en blanc. La deuxième table ressemble à ceci avec le 7s comme des blancs:

000055  
11  55  
11  55  
116655  
22  33  
22  33  
444433  

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

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

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

  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 inférieurs sont utilisés, cela n'affectera pas la sortie); cela donne "?{{?}}?gg::799G" (notez que le 8ème caractère est inutilisé, donc nous pouvons réellement faire ce 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 l'espérions. 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.


1768
2018-03-13 19:46



Formalisons 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 classiquement) est 1. main() va s'appeler récursivement, en passant le résultat de -(~_) (NOT bitwise négatif de _), donc vraiment ça ira 448 récursions (Seule condition où _^448 == 0).

Prenant cela, il va imprimer 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');
}

À présent, 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 une autre affiche a dit, __TIME__ est l'heure de compilation pour le programme, et est une chaîne, donc il y a une certaine arithmétique de chaîne qui se passe, tout en profitant d'un indice bidirectionnel: a [b] est le même que b [a] pour les tableaux de caractères.

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

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


97
2018-03-13 21:11



Ajout 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.


46
2018-03-14 23:54



J'ai désobscuqué autant que possible l'arithmétique modulo et j'ai 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');
}

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

3
2018-04-21 14:48