<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>C : coups de cœur et coups de gueule &#187; Divers</title>
	<atom:link href="https://blog.developpez.com/kirilenko/pcategory/divers/feed" rel="self" type="application/rss+xml" />
	<link>https://blog.developpez.com/kirilenko</link>
	<description>Write in C!</description>
	<lastBuildDate>Sat, 20 Apr 2013 16:54:09 +0000</lastBuildDate>
	<language>fr-FR</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>https://wordpress.org/?v=4.1.42</generator>
	<item>
		<title>Récursivité en C, épidémie ou hérésie ?</title>
		<link>https://blog.developpez.com/kirilenko/p11930/divers/recursivite-en-c-epidemie-ou-heresie</link>
		<comments>https://blog.developpez.com/kirilenko/p11930/divers/recursivite-en-c-epidemie-ou-heresie#comments</comments>
		<pubDate>Sat, 20 Apr 2013 16:12:40 +0000</pubDate>
		<dc:creator><![CDATA[Kirilenko]]></dc:creator>
				<category><![CDATA[Divers]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[main]]></category>
		<category><![CDATA[récursion]]></category>
		<category><![CDATA[récursivité]]></category>

		<guid isPermaLink="false">http://blog.developpez.com/kirilenko/?p=12</guid>
		<description><![CDATA[Une récursion pour les gouverner tous Voilà quelques temps que je n&#8217;avais plus utilisé ce média ; j&#8217;avais posté un article similaire sur une plateforme perdue, mais, n&#8217;ayant pas reçu réellement de retour, je me suis dit qu&#8217;il serait intéressant de centraliser tous mes petits articles ici, de manière à renvoyer uniformément les visiteurs sur cette page. Au départ, il s&#8217;agissait ici de parler uniquement de programmation système ; le sort en a décidé autrement. [&#8230;]]]></description>
				<content:encoded><![CDATA[<h2>Une récursion pour les gouverner tous</h2>
<p>Voilà quelques temps que je n&rsquo;avais plus utilisé ce média ; j&rsquo;avais posté un article similaire sur une plateforme perdue, mais, n&rsquo;ayant pas reçu réellement de retour, je me suis dit qu&rsquo;il serait intéressant de centraliser tous mes petits articles ici, de manière à renvoyer uniformément les visiteurs sur cette page. Au départ, il s&rsquo;agissait ici de parler uniquement de programmation système ; le sort en a décidé autrement.</p>
<p>En attendant, vous allez devoir me pardonner pour cette digression, qui ne devrait pas vraiment avoir de répercussion sur les visites, ceci dit. Voilà une nouvelle catégorie, « Divers », qui devrait accueillir nombre de mes réflexions inutiles (sur les forums, il est difficile de se lâcher sans se faire taper sur les doigts).</p>
<p>Qu&rsquo;on se mette d&rsquo;accord, aujourd&rsquo;hui, c&rsquo;est d&rsquo;un véritable phénomène qu&rsquo;on va parler. Une habitude étrange que quelques débutants ont prise : effectuer une récursion sur la fonction <em>main</em>. Ce sera notre prétexte pour explorer tout le vaste sujet de la récursivité en C. Je ne sais pas qui le leur a inculqué (ou plutôt, qui n&rsquo;a pas fait son boulot en le leur empêchant) ; toujours est-il que cela fait frémir la plupart des programmeurs « habitués » sur les forums. Si ce n&rsquo;est pas votre cas, c&rsquo;est inquiétant. Du moins, on va essayer de voir si c&rsquo;est le cas. Et surtout, pourquoi.</p>
<p><span id="more-12"></span></p>
<h2>Une récursion pour les trouver</h2>
<p>Vous êtes ici, sur un billet qui parle du C, et on vous parle d&rsquo;algorithmique ? Voilà qui semble, à première vue, plutôt contradictoire. Après tout, les gens qui font du C sont plutôt branchés sur le matériel. C&rsquo;est ça, le (faux) bas niveau. Seulement, la récursivité, c&rsquo;est, avant toute chose, une manière particulière de définir un algorithme donné, en le définissant par rapport à une entrée plus simple du problème. En programmation, on parlera de récursivité lorsqu&rsquo;une fonction s&rsquo;appelle elle-même. Parfois, il y a même des cas de récursivité croisée, ou deux fonctions s&rsquo;appellent mutuellement. Un exemple plutôt connu est la suite d&rsquo;Hofstadter.</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">int female(int n)<br />
{<br />
&nbsp; &nbsp; if (n == 0) return 1;<br />
&nbsp; &nbsp; return n - male(female(n - 1));<br />
}<br />
<br />
int male(int n)<br />
{<br />
&nbsp; &nbsp; if (n == 0) return 0;<br />
&nbsp; &nbsp; return n - female(male(n - 1));<br />
}</div></div>
<p>Prenons maintenant l&rsquo;exemple (sérieux) d&rsquo;un algorithme calculant la <strong>factorielle</strong> d&rsquo;un nombre entier naturel. Vous pouvez la définir de deux manières différentes :</p>
<p>1) <strong>Itérativement</strong> : n! = 1 * 2 * &#8230; * n.</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">int factorial_1(int n)<br />
{<br />
&nbsp; &nbsp; int r = 1, i;<br />
&nbsp; &nbsp; for (i = 1; i &amp;lt; n; i++) r *= i;<br />
&nbsp; &nbsp; return r;<br />
}</div></div>
<p>2) <strong>Récursivement</strong> : n! = 1 si n = 0 et n * (n &#8211; 1)! si n &gt; 0.</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">int factorial_2(int n)<br />
{<br />
&nbsp; &nbsp; if (n == 0) return 1;<br />
&nbsp; &nbsp; return n * factorial_2(n - 1);<br />
}</div></div>
<p>Dès ces cas d&rsquo;école, les puristes commencent à remettre en cause l&rsquo;efficacité de la solution récursive. En effet, par nature, le C est assez proche de la machine. Chaque appel de fonction nécessite, en théorie, la création d&rsquo;un <strong>cadre de pile</strong> (en pratique, certaines de ces fonctions peuvent être <em>inlinées</em> par le compilateur, c&rsquo;est-à-dire en ajoutant le code directement dans la fonction appelante). </p>
<p>Ce n&rsquo;est pas une opération particulièrement légère, et, dès qu&rsquo;on se met à penser à la micro-optimisation, il vaut mieux éviter. En dehors de la performance, cela pose un autre problème : celui de la mémoire. Il faut bien voir que toutes les variables automatiques, les registres de base et de retour notamment, doivent être stockés sur la pile, et ce pour chaque récursion en cours.</p>
<p>En conséquence, le compilateur essaye de transformer des cas récursifs simples en boucles (qui, au final, auront l&rsquo;air d&rsquo;un branchement inconditionnel, équivalent à un vulgaire <em>goto</em>). Il est bien connu que tout appel récursif peut être converti en itératif, par l&rsquo;intermédiaire de piles (en recodant la pile d&rsquo;appels en fait), mais, naturellement, ça n&rsquo;aurait pas de sens pour le compilateur de faire ce genre d&rsquo;optimisations. Cependant, supposez que vous ayez choisi de coder la factorielle de cette manière :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">int factorial_3(int r, int n)<br />
{<br />
&nbsp; &nbsp; if (n == 0) return r;<br />
&nbsp; &nbsp; return factorial_2(r * n, n - 1);<br />
}</div></div>
<p>Parce que <em>r</em> indique les résultats de la récursivité au fur et à mesure, on dit qu&rsquo;il sert d&rsquo;<strong>accumulateur</strong>. En outre, on remarque ici que l&rsquo;appel récursif peut être facilement évité : il suffit de changer la valeur de <em>r</em> et de <em>n</em> à chaque fin de branchement. Mettons-nous à la place du compilateur optimisant (c&rsquo;est le cas de la plupart des compilateurs « grands publics » comme <em>gcc</em>, <em>clang</em> ou <em>icc</em>). Voilà comment il pourrait réécrire le code.</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">int factorial_4(int r, int n)<br />
{<br />
I1:<br />
&nbsp; &nbsp; if (n == 0) return r;<br />
&nbsp; &nbsp; r = r * n;<br />
&nbsp; &nbsp; n = n - 1;<br />
&nbsp; &nbsp; goto I1;<br />
}</div></div>
<p>Je ne suis pas certain que beaucoup d&rsquo;entre vous aimerait maintenir ce genre de code, mais, pour la bêtise absolue d&rsquo;un ordinateur, ce n&rsquo;est pas dérageant. Au contraire, comme sa fainéantise est légendaire, il va se réjouir de pas avoir à créer un nouveau cadre de pile à chaque fois qu&rsquo;on décrémente <em>n</em>. On parle dans ce cas de <strong>récursivité terminale</strong>, et ce n&rsquo;est pas le cas de la fonction <em>factorial_2</em> ci-dessus. Bref, tout ça pour dire quoi ? Que la récursivité, c&rsquo;est intrinsèquement de l&rsquo;algorithmique. Ça fait plaisir aux férus de la programmation fonctionnelle, mais ça ne devrait pas atteindre nos points d&rsquo;entrée&#8230; Enfin, en théorie.</p>
<p><!--more--></p>
<h2>Une récursion pour les amener tous</h2>
<p>Venons-en au fait. Enfin, presque. Oui, parce que je voulais vous présenter mon nouveau jeu en console puissant. Du coup, j&rsquo;ai fait une jolie interface avec une fonction juste pour le menu. Voilà à quoi ressemble notre bête :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">#include <br />
<br />
int menu(void)<br />
{<br />
&nbsp; &nbsp; int n;<br />
&nbsp; &nbsp; puts(&quot;Que voulez-vous faire ?\n&quot;<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&quot; 1 - Manger un caillou.\n&quot;<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&quot; 2 - Lécher un stylo.\n&quot;<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&quot; 3 - Appeler un koala.\n<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&quot;Votre choix ?&quot;);<br />
&nbsp; &nbsp; scanf(&quot;%d&quot;, &amp;amp;n);<br />
&nbsp; &nbsp; if (n &nbsp;3) return menu();<br />
&nbsp; &nbsp; return n;<br />
}</div></div>
<p>Sauf que, si je laisse ça comme ça, il y a danger. Danger que la pile explose, en réalité. Elle a une taille limitée, et un utilisateur malicieux pourrait même exploiter ces débordements pour écrire par-dessus certaines données (mais ça rentre dans des techniques d&rsquo;exploitation plutôt sordides). Dans une certaine mesure, même dans les langages fonctionnels (oui, oui), on évite généralement les systèmes récursifs pour lire des données structurées comme les tableaux. Pour les listes ou les arbres, c&rsquo;est différent, mais, même en C, la question se pose.</p>
<p>En fait, toutes ces histoires de puristes restent extrêmement subjectives. Notre fonction <em>menu</em> ci-dessus croît selon un majorant en <em>O(n)</em>. En revanche, il existe certaines fonctions qui ont une complexité bien moindre. C&rsquo;est par exemple le cas de la <strong>recherche dichotomique</strong>, qui permet de rechercher une valeur dans un tableau trié en complexité logarithmique (par le biais du paradigme « diviser pour régner »). Ainsi, cela ne posera pas trop de problème à un programmeur d&rsquo;écrire :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">int search_divide(const int *a, int left, int right, int element)<br />
{<br />
&nbsp; &nbsp; int mid;<br />
&nbsp; &nbsp; if (left &amp;gt; right) return -1;<br />
&nbsp; &nbsp; mid = (left + right) / 2;<br />
&nbsp; &nbsp; if (a[mid] &amp;gt; element) return search_divide(a, left, mid - 1);<br />
&nbsp; &nbsp; if (a[mid] &amp;lt; element) return search_divide(a, mid + 1, right);<br />
&nbsp; &nbsp; return mid;<br />
}</div></div>
<p>Pourtant, la solution itérative est tout aussi limpide. Du coup, la récursivité se justifie encore plus facilement quand on est obligé de sortir un modèle à base de pile pour la conversion. C&rsquo;est par exemple le cas des <em>parsers</em> d&rsquo;expression, quand on doit explorer des caractères sémantiques par définition récursifs (comme les parenthèses dans un <em>parser</em> d&rsquo;expressions mathématiques).</p>
<p><!--more--></p>
<h2>&#8230; et dans les ténèbres les lier</h2>
<p>Il est temps de discuter ce qui devait être notre objet initial, mais, qui, finalement, est passé au travers de ma langue bavarde. Oui, parce qu&rsquo;il existe des pauvres fous (fuyez !) qui, non contents de faire de la récursion sur des menus, le font dans la fonction <em>main</em> ! On voit quelques fois un exemple de récursivité croisée, quand les débutants veulent revenir au début du programme, alors qu&rsquo;ils sont perdus dans des structures conditionnelle imbriquées.</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">void foo(void)<br />
{<br />
&nbsp; &nbsp; /* Some stuff */<br />
&nbsp; &nbsp; if (repeat) main();<br />
}</div></div>
<p>Le mieux, dans ce genre de code, c&rsquo;est de passer à la boucle (en ré-arrangeant le code dans un premier temps, évidement&#8230;) ; cela pour deux raisons majoritairement :</p>
<li>L&rsquo;interfaçage avec le C++ : la norme de ce langage interdit spécifiquement d&rsquo;appeler <em>main</em>.</li>
<li>Les implémentations non conformes : en embarqué, certains compilateurs non standards insèrent des bouts de code d&rsquo;initialisation au début du programme (c&rsquo;est du déjà vu).</li>
<p>Pour finir, j&rsquo;aimerais simplement vous montrer un petit contre-exemple, que quelqu&rsquo;un proposait sur <em>Usenet</em>. Le principe est d&rsquo;utiliser la récursivité sur la fonction <em>main</em> pour simplifier le traitement des arguments en ligne de commande. Personnellement, je n&rsquo;ai jamais eu affaire à ce genre de contraintes, mais on ne sait jamais sur quoi on peut tomber (sans <em>getopt</em>, certains sont perdus !).</p>
<p>Voilà qui clôt magistralement mal notre modeste billet. J&rsquo;espère ne pas trop vous avoir ennuyé, et n&rsquo;oubliez pas de rediriger le prochain inconscient par ici&#8230; non sans l&rsquo;avoir châtié sévèrement, évidemment !</p>
<p>PS : Comme habituellement, ça fait toujours du plaisir de voir ses posts commentés&#8230; Même si vous n&rsquo;avez rien à dire, ça donne l&rsquo;impression qu&rsquo;on ne parle pas dans le vide. Sauf si vous ne voulez plus jamais me voir appuyer sur une touche du clavier, auquel cas je vous laisse déverser votre haine dans le silence. Le train de vos injures roule sur les rails de mon indifférence, disait un proverbe populaire. <img src="https://blog.developpez.com/kirilenko/wp-includes/images/smilies/icon_smile.gif" alt=":-)" class="wp-smiley" /></p>
]]></content:encoded>
			<wfw:commentRss></wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
	</channel>
</rss>
