Table des matières
Un miroir ? Euh on parle de quoi là, d'un dépôt de fichiers ? Non, pas du tout ! Je vais vous présenter un programme dont la source est identique à la sortie console. Uh ?
Le problème
Si nous avons une source C comme suit :
#include <stdio.h> #include <stdlib.h> int main() { [ .... ] /* Tout plein de code ici */ return 0; } |
On veut que la console affiche exactement la même chose, quelque soit le code entre [ .... ] :
#include <stdio.h> #include <stdlib.h> int main() { [ .... ] /* Tout plein de code ici */ return 0; } |
Cela paraît chose aisée, mais ça demande pas mal de réflexion (j'ai mis beaucoup de temps à trouver la solution !). Si vous voulez chercher par vous même, ne lisez pas la suite.
La réflexion
La base de la solution : la fonction printf(). Ca, tout le monde s'en serait douté. Mais la solution aurait été beaucoup plus compliqué avec un std::cout de c++ (voire impossible, je n'y ai pas vraiment réfléchi). Il va falloir tirer parti du couple de paramètres format / chaine de paramètres variables (les ... quand on regarde la syntaxe d'appel de la fonction !). Si vous voulez en savoir plus à ce sujet, je vous conseille de regarder ceci : http://msdn.microsoft.com/en-us/library/kb57fad8(VS.71).aspx. Ce n'est PAS une librairie exclusive MS ou quelque chose du genre, comme vous le verrez dans l'exemple, MS donne également un exemple sous UNIX.
Voici donc l'idée de départ : utiliser une variable à la fois comme format et comme paramètre de printf :
#include <stdio.h> #include <stdlib.h> int main() { const char* var = "#include <stdio.h> #include <stdlib.h> int main() { const char* var = "%s"; printf(var, var); return 0; }"; printf(var, var); return 0; } |
Bien sûr ca ne fonctionne pas, on ne peut pas définir une chaîne sur plusieurs lignes 😉 Et puis les tabulations / nouvelles lignes ne seraient pas interprétées. Deuxième étape donc : mettre la variable sur une ligne
#include <stdio.h> #include <stdlib.h> int main() { const char* var = "#include <stdio.h>\n#include <stdlib.h>\nint main()\n{\n\tconst char* var = \"%s\";\n\tprintf(var, var);\n\tsystem(\"pause\");\n\treturn 0;\n}"; printf(var, var); system("pause"); return 0; } |
Ce code fonctionne. Voici la sortie que vous devriez obtenir :
#include <stdio.h> #include <stdlib.h> int main() { const char* var = "#include <stdio.h> #include <stdlib.h> int main() { const char* var = "%s"; printf(var, var); system("pause"); return 0; }"; printf(var, var); system("pause"); return 0; }Appuyez sur une touche pour continuer... |
Le system("pause"); c'est pas nécessaire, un fail de ma part.
Sur le principe, "l'énigme" est résolue, cependant la mise en forme n'est pas la même que dans notre code ! Il se pose donc une autre problématique. Comment faire en sorte que le const char* var affiché en console montre les \n les \t etc ?
La solution
Il va nous falloir créer une sorte de addslashes en c (pour les amateurs de php x) )
Voici donc le code final. Je vous passe les étapes pour en arriver là. Si vous ne comprenez pas tout demandez. Le code est un peu "condensé"/"compacté" pour la mise en ligne dans la variable
#include <stdio.h> #include <stdlib.h> #include <string.h> char* strrep(const char *str) { char ret[4096]; int oi=0, ri=0; for (;;) { if(str[oi] == 10) { ret[ri]=92; ret[++ri]='n'; } else if (str[oi] == 9) { ret[ri]=92; ret[++ri]='t'; } ri++; oi++; else if (str[oi] == 34) { ret[ri]=92; ret[++ri]=34; } else ret[ri] = str[oi]; if(oi>=strlen(str)) break; } ret[ri]=0; return ret; } int main() { const char* var = "#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\nchar* strrep(const char *str) {\n\tchar ret[4096]; int oi=0, ri=0;\n\tfor (;;)\n\t{\n\t\tif(str[oi] == 10) { ret[ri]=92; ret[++ri]='n'; }\n\t\telse if (str[oi] == 9) { ret[ri]=92; ret[++ri]='t'; }\n\t\telse if (str[oi] == 34) { ret[ri]=92; ret[++ri]=34; }\n\t\telse ret[ri] = str[oi];\n\t\tri++; oi++;\n\t\tif(oi>=strlen(str)) break;\n\t}\n\tret[ri]=0; return ret;\n}\nint main()\n{\n\tconst char* var = \"%s\";\n\tprintf(var, strrep(var));\n\treturn 0;\n}"; printf(var, strrep(var)); return 0; } |
et voici la sortie console :
#include <stdio.h> #include <stdlib.h> #include <string.h> char* strrep(const char *str) { char ret[4096]; int oi=0, ri=0; for (;;) { if(str[oi] == 10) { ret[ri]=92; ret[++ri]='n'; } else if (str[oi] == 9) { ret[ri]=92; ret[++ri]='t'; } ri++; oi++; else if (str[oi] == 34) { ret[ri]=92; ret[++ri]=34; } else ret[ri] = str[oi]; if(oi>=strlen(str)) break; } ret[ri]=0; return ret; } int main() { const char* var = "#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\nchar* strrep(const char *str) {\n\tchar ret[4096]; int oi=0, ri=0;\n\tfor (;;)\n\t{\n\t\tif(str[oi] == 10) { ret[ri]=92; ret[++ri]='n'; }\n\t\telse if (str[oi] == 9) { ret[ri]=92; ret[++ri]='t'; }\n\t\telse if (str[oi] == 34) { ret[ri]=92; ret[++ri]=34; }\n\t\telse ret[ri] = str[oi];\n\t\tri++; oi++;\n\t\tif(oi>=strlen(str)) break;\n\t}\n\tret[ri]=0; return ret;\n}\nint main()\n{\n\tconst char* var = \"%s\";\n\tprintf(var, strrep(var));\n\treturn 0;\n}"; printf(var, strrep(var)); return 0; } |
Parfaitement identiques ! Désormais vous pouvez ajouter autant de code que vous le désirez, tout ce qu'il reste à faire est de l'ajouter également dans la variable var (et de le formatter bien entendu).
Rapide explication du nom des variables et de ce que fait le code ajouté : la fonction strrep (quand même ^^) :
oi = original index (l'index de la lettre courante dans la variable str passée en paramètre)
ri = return index (l'index de la lettre courante dans la variable retournée)
code ascii 92 = l'antislash
code ascii 10 = retour à la ligne (sous windows ! il se peut qu'il s'agisse de 13 sous linux et des deux sous mac, si mes souvenirs sont bons)
code ascii 9 = antislash t = tabulation
code ascii 34 = guillemets doubles
La boucle passe chacun des caractères du buffer d'entrée. S'il s'agit d'un caractère spécial (que nous avons écrit \t, \n ou \"), il est décomposé en 2 caractères ascii pour être affiché et non interprété.
Le ret[ri]=0 de la fin ajoute le caractère de fin de chaine pour le printf
Voilà, faites passer l'énigme, ne divulguez pas (directement) la solution, et réfléchissez un peu quand même =)
ÉDIT du 15/07/2012 à 21h30 par GuiGui : Si vous voulez en savoir plus et/ou voir d'autres exemples, je vous conseille ce billet : Programmes auto-reproducteurs (quines) chez Rom1v