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 le temps une fois compilé. Sur mon système:
!! !!!!!! !! !!!!!! !! !!!!!!
!! !! !! !! !! !! !! !!
!! !! !! !! !! !! !! !!
!! !!!!!! !! !! !! !! !! !!!!!!
!! !! !! !! !! !! !!
!! !! !! !! !! !! !!
!! !!!!!! !! !! !! !!!!!!
Source: sykes2 - une horloge sur une ligne , conseils de l'auteur sykes2
Quelques astuces: Pas de compilation des avertissements 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é-obscurcissons-le.
Mise 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 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
_ en raison de 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));
}
}
_
Notez maintenant que a[b]
_ EST IDENTIQUE À _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écursivité en boucle et simplifier un peu plus:
_// 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 le contenu à afficher et met 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 seconde table (_";;;====~$::199"
_) sélectionne le bit approprié à afficher à partir de la bitmap.
Commençons par examiner le second tableau, int shift = ";;;====~$::199"[(i*2&8) | (i/64)];
. _i/64
_ est le numéro de ligne (6 à 0) et _i*2&8
_ est 8 iff i
est 4, 5, 6 ou 7 mod 8.
if((i & 2) == 0) shift /= 8; shift = shift % 8
sélectionne le chiffre octal supérieur (pour _i%8
_ = 0,1,4,5) ou le chiffre octal inférieur (pour _i%8
_ = 2,3,6,7) du 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 (sournois!).
Ceci est conçu après un affichage à sept segments, avec _7
_ s en tant que blancs. Les entrées du premier tableau doivent donc définir les segments qui s’allument.
__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 ASCII valeurs 48 à 57 et _:
_ a ASCII valeur 58. La sortie contient 64 caractères par ligne, ce qui laisse 8 caractères par caractère de ___TIME__
_.
_7 - i/8%8
_ est donc l'index de ___TIME__
_ en cours de sortie (le _7-
_ est nécessaire car nous effectuons une itération de i
vers le bas). Donc, t
est le caractère de ___TIME__
_ en 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 numéro est un bitmap décrivant les segments allumés dans notre affichage à sept segments. Les caractères étant tous des caractères ASCII 7 bits, le bit haut est toujours effacé. Ainsi, _7
_ dans la table de segments s'imprime toujours comme un blanc. La deuxième table ressemble à ceci avec le _7
_ s en blanc:
_000055
11 55
11 55
116655
22 33
22 33
444433
_
Ainsi, par exemple, _4
_ est _01101010
_ (bits 1, 3, 5 et 6 définis), qui affiche
_----!!--
!!--!!--
!!--!!--
!!!!!!--
----!!--
----!!--
----!!--
_
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é en tant que _"?;;?==? '::799\x07"
_. Pour des raisons artistiques, nous ajouterons 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 créer comme nous le 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 le tableau qu'il a utilisé.
Formons ceci pour en 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 argument, _ (argc de manière conventionnelle) est 1
. main()
s'appellera de manière récursive en transmettant le résultat de -(~_)
(PAS au niveau du bit négatif de _
), si bien qu'il y aura 448 récursions (condition unique où _^448 == 0
).
En prenant cela, cela affichera 7 lignes larges de 64 caractères (la condition ternaire extérieure 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 un nombre décimal pour ASCII espace. Il imprime un espace ou un '!' (33 est '!', D'où le '&1
' à la fin). Concentrons-nous sur la goutte 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 moment de la compilation du programme. Il s'agit d'une chaîne. Il y a donc une arithmétique de chaîne en cours, ainsi que l'avantage d'un indice de tableau bidirectionnel: a [b] est identique comme 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__
. Ceci 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. Cette même manipulation de code ASCII _ du caractère se poursuit tout au long de l’expression, afin d’imprimer un '' ou un '!' en fonction de 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 à 2, donc -~x
est -(-1-x) = x+1
.
J'ai dés-obscurci autant que possible l'arithmétique modulo et enlevé la récursion
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');
}
Développer 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');
}