Une application miroir en C

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

Aucun commentaire.

Ajoutez votre commentaire