®om's blog 2017-07-16T10:24:01+02:00 http://blog.rom1v.com Romain Vimont rom@rom1v.com Fusionner deux dépôts git 2017-07-12T20:30:00+02:00 http://blog.rom1v.com/2017/07/fusionner-deux-depots-git <p>Ce billet explique comment fusionner un dépôt <em>git</em> (avec son historique) dans un sous-répertoire d’un autre dépôt <em>git</em>.</p> <h2 id="cas-dusage">Cas d’usage</h2> <p>Mon projet principal se trouve dans un dépôt <code class="highlighter-rouge">main</code>. J’ai démarré dans un autre dépôt un projet <code class="highlighter-rouge">other</code>, que je souhaite finalement intégrer dans un sous-répertoire <code class="highlighter-rouge">sub/</code> du projet principal, en conservant son historique. Après cette fusion, je ne garderai que le dépôt principal.</p> <h2 id="fusion">Fusion</h2> <p>Les deux projets se trouvent dans le répertoire courant :</p> <div class="highlighter-rouge"><pre class="codehilite"><code>$ ls main other </code></pre></div> <p>Dans le dépôt <code class="highlighter-rouge">main</code>, <em>copier</em> la branche <code class="highlighter-rouge">master</code> de <code class="highlighter-rouge">other</code> dans une nouvelle branche <code class="highlighter-rouge">tmp</code> :</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nb">cd </span>main git fetch ../other master:tmp</code></pre></figure> <p>Le dépôt <code class="highlighter-rouge">main</code> contient alors les historiques disjoints des deux projets.</p> <p>Nous allons maintenant réécrire l’historique complet de la branche <code class="highlighter-rouge">tmp</code> pour déplacer tout le contenu dans un sous-répertoire <code class="highlighter-rouge">sub/</code>, grâce une commande donnée en exemple de <a href="https://git-scm.com/docs/git-filter-branch#_examples"><code class="highlighter-rouge">man git filter-branch</code></a> :</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash">git checkout tmp git filter-branch --index-filter <span class="se">\</span> <span class="s1">'git ls-files -s | sed "s-\t\"*-&amp;sub/-" | GIT_INDEX_FILE=$GIT_INDEX_FILE.new \ git update-index --index-info &amp;&amp; mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"'</span></code></pre></figure> <p>À ce stade, nous avons toujours deux historiques indépendants, mais le contenu lié à la branche <code class="highlighter-rouge">tmp</code> se trouve dans le sous-répertoire <code class="highlighter-rouge">sub/</code>.</p> <div class="highlighter-rouge"><pre class="codehilite"><code>A---B---C---D master X---Y---Z tmp </code></pre></div> <p>La dernière étape consiste à relier les deux historiques, soit grâce à un <em>rebase</em>, soit grâce à un <em>merge</em>.</p> <p>Un <em>rebase</em> réécrit l’historique du sous-projet sur la branche <code class="highlighter-rouge">master</code> :</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash">git rebase master <span class="c"># A---B---C---D---X'--Y'--Z' master</span></code></pre></figure> <p>Un <em>merge</em> relie juste les deux historiques grâce à un commit de <em>merge</em> :</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash">git merge tmp --allow-unrelated-histories <span class="c"># A---B---C---D---M master</span> <span class="c"># /</span> <span class="c"># X---Y---Z tmp</span></code></pre></figure> <h2 id="concrtement">Concrètement</h2> <p>J’ai débuté la réécriture du serveur relais de <a href="/2017/03/gnirehtet/">gnirehtet</a> en <a href="https://www.rust-lang.org">Rust</a> dans un dépôt séparé. Maintenant qu’il commence à fonctionner, je l’ai fusionné dans un <a href="https://github.com/Genymobile/gnirehtet/tree/rust/rustrelay">sous-répertoire</a> du <a href="https://github.com/Genymobile/gnirehtet">dépôt principal</a> tout en conservant l’<a href="https://github.com/Genymobile/gnirehtet/commits/rust">historique</a> :</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash">git fetch ../rustrelay master:tmp git checkout tmp git filter-branch --index-filter <span class="se">\</span> <span class="s1">'git ls-files -s | sed "s-\t\"*-&amp;rustrelay/-" | GIT_INDEX_FILE=$GIT_INDEX_FILE.new \ git update-index --index-info &amp;&amp; mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"'</span> git rebase master</code></pre></figure> Gnirehtet 2017-03-30T11:50:00+02:00 http://blog.rom1v.com/2017/03/gnirehtet <p>Durant ces dernières semaines chez <a href="https://www.genymobile.com/">Genymobile</a>, j’ai développé un outil de <em>reverse tethering</em> pour Android, permettant aux téléphones (et aux tablettes) d’utiliser la connexion internet de l’ordinateur sur lequel ils sont branchés, sans accès <em>root</em> (ni sur le téléphone, ni sur le PC). Il fonctionne sur <em>GNU/Linux</em>, <em>Windows</em> et <em>Mac OS</em>.</p> <p>Nous avons décidé de le publier en open source, sous le nom de <a href="https://github.com/Genymobile/gnirehtet"><em>gnirehtet</em></a>.</p> <p><em>Oui, c’est un nom bizarre, jusqu’à ce qu’on réalise qu’il s’agit du résultat de la commande <a href="https://fr.wikipedia.org/wiki/Bourne-Again_shell">bash</a> :</em></p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash">rev <span class="o">&lt;&lt;&lt;</span> tethering</code></pre></figure> <h2 id="utilisation">Utilisation</h2> <p>Il suffit de télécharger la dernière <a href="https://github.com/Genymobile/gnirehtet/releases/latest">release</a>, de l’extraire, et d’exécuter la commande suivante sur le PC :</p> <div class="highlighter-rouge"><pre class="codehilite"><code>./gnirehtet rt </code></pre></div> <p>Une fois activé, un logo en forme de clé apparaît dans la barre de statut du téléphone :</p> <p class="center"><img src="/assets/gnirehtet/key.png" alt="key" /></p> <p>Lisez le fichier <a href="https://github.com/Genymobile/gnirehtet/blob/master/README.md">README</a> pour plus de détails.</p> <h2 id="fonctionnement">Fonctionnement</h2> <p>Le projet est composé de deux parties :</p> <ul> <li>une application Android (le client) ;</li> <li>une application Java pour le PC (le serveur relais).</li> </ul> <p>Le client s’enregistre en tant que VPN, de manière à intercepter tout le trafic réseau du téléphone, sous la forme de <code class="highlighter-rouge">byte[]</code> de <a href="https://en.wikipedia.org/wiki/IPv4#Packet_structure">paquets IPv4</a> bruts, qu’il transmet alors vers le serveur relais sur une connexion <a href="https://fr.wikipedia.org/wiki/Transmission_Control_Protocol">TCP</a> (établie par-dessus <a href="https://developer.android.com/studio/command-line/adb.html"><em>adb</em></a>).</p> <p>Le serveur relais analyse les en-têtes des paquets, ouvre des connexions à partir du PC vers les adresses de destinations demandées, et relaie le contenu dans les deux sens en suivant les protocoles <a href="https://fr.wikipedia.org/wiki/User_Datagram_Protocol">UDP</a> et <a href="https://fr.wikipedia.org/wiki/Transmission_Control_Protocol">TCP</a>. Il crée et renvoie des paquets de réponse vers le client Android, qui les écrit sur l’interface VPN.</p> <p>D’une certaine manière, le serveur relais se comporte comme un <a href="https://fr.wikipedia.org/wiki/Network_address_translation">NAT</a>, en cela qu’il ouvre des connexions pour le compte d’autres machines qui n’ont pas accès au réseau. Cependant, il diffère des NAT standards dans la manière dont il communique avec les clients, en utilisant un protocole spécifique (très simple) sur une connexion TCP.</p> <p class="center"><img src="/assets/gnirehtet/archi.png" alt="archi" /></p> <p>Pour plus de détails, lisez la <a href="https://github.com/Genymobile/gnirehtet/blob/master/DEVELOP.md">page développeurs</a>.</p> <h2 id="conception">Conception</h2> <p>Une fois que l’application est capable d’intercepter l’intégralité du traffic réseau du téléphone, différentes approches sont possibles. Voici celles que j’ai considérées.</p> <p><em><strong>TL;DR:</strong> J’ai d’abord étudié l’utilisation d’un “TUN device” sur le PC, mais ça ne répondait pas à nos besoins. J’ai ensuite voulu utiliser <a href="https://fr.wikipedia.org/wiki/SOCKS">SOCKS</a> pour bénéficier des serveurs existants, mais des contraintes nous empêchaient de relayer le trafic UDP. Alors j’ai implémenté <a href="https://github.com/Genymobile/gnirehtet">gnirehtet</a>.</em></p> <h3 id="tun-device">TUN device</h3> <p>Lors de mes recherches pour savoir comment implémenter le <em>reverse tethering</em>, j’ai d’abord trouvé des projets créant un <a href="https://en.wikipedia.org/wiki/TUN/TAP">TUN device</a> sur l’ordinateur (<a href="https://github.com/google/vpn-reverse-tether"><code class="highlighter-rouge">vpn-reverse-tether</code></a> and <a href="https://github.com/vvviperrr/SimpleRT"><code class="highlighter-rouge">SimpleRT</code></a>).</p> <p>Cette conception fonctionne très bien, et a plusieurs avantages :</p> <ul> <li>le traitement est effectué directement au niveau réseau, donc il n’y a pas besoin de traduction entre le niveau 3 et le niveau 5 du <a href="https://fr.wikipedia.org/wiki/Mod%C3%A8le_OSI">modèle OSI</a> ;</li> <li>tous les paquets sont retransmis, indépendamment de leur protocole de transport (ils sont donc <a href="https://en.wikipedia.org/wiki/List_of_IP_protocol_numbers">tous</a> supportés, là où <em>gnirehtet</em> ne supporte “que” <a href="https://fr.wikipedia.org/wiki/Transmission_Control_Protocol">TCP</a> et <a href="https://fr.wikipedia.org/wiki/User_Datagram_Protocol">UDP</a>).</li> </ul> <p>Cependant :</p> <ul> <li>elle nécessite un accès <em>root</em> sur l’ordinateur ;</li> <li>elle ne fonctionne pas sur autre chose que <em>Linux</em>.</li> </ul> <p>Il se peut néanmoins que ces applications répondent davantage à vos besoins.</p> <h3 id="socks">SOCKS</h3> <p>Afin d’éviter d’avoir à développer un serveur relais spécifique, ma première idée était d’écrire un client qui parlait le protocole <a href="https://fr.wikipedia.org/wiki/SOCKS">SOCKS</a> (suivant le <a href="https://tools.ietf.org/html/rfc1928">RFC 1928</a>). Ainsi, il serait possible d’utiliser n’importe quel serveur SOCKS existant, par exemple celui fourni par <code class="highlighter-rouge">ssh -D</code>.</p> <p>Vous l’avez probablement déjà utilisé pour éviter le filtrage des pare-feux en entreprise. Pour cela, démarrez le tunnel :</p> <div class="highlighter-rouge"><pre class="codehilite"><code>ssh mon_serveur -ND1080 </code></pre></div> <p>Puis configurez votre navigateur pour utiliser le proxy SOCKS <code class="highlighter-rouge">localhost:1080</code>. N’oubliez pas d’activer la résolution DNS distante pour résoudre les noms de domaine à partir de <code class="highlighter-rouge">mon_serveur</code> (dans <em>Firefox</em>, activez <code class="highlighter-rouge">network.proxy.socks_remote_dns</code> dans <code class="highlighter-rouge">about:config</code>).</p> <p>Malheureusement, l’implémentation d’<a href="https://fr.wikipedia.org/wiki/OpenSSH">OpenSSH</a> ne <a href="http://lists.mindrot.org/pipermail/openssh-unix-dev/2017-January/035662.html">supporte pas UDP</a>, même si le protocole <a href="https://fr.wikipedia.org/wiki/SOCKS#SOCKS_v5">SOCKS5</a> lui-même le supporte. Et nous avons besoin d’UDP, au moins pour les requêtes <a href="https://fr.wikipedia.org/wiki/Domain_Name_System">DNS</a> (ainsi que pour <a href="https://fr.wikipedia.org/wiki/Network_Time_Protocol">NTP</a>).</p> <p>Si vous avez lu attentivement les deux paragraphes précédents, vous devriez vous demander :</p> <blockquote> <p>Comment Firefox peut-il résoudre les noms de domaine à distance alors que le proxy SOCKS d’OpenSSH ne supporte même pas UDP ?</p> </blockquote> <p>La réponse se trouve dans la <a href="https://tools.ietf.org/html/rfc1928#section-4">section 4</a> du RFC : l’adresse de destination demandée peut être une IPv4, une IPv6 ou <strong>un nom de domaine</strong>. Par contre, pour utiliser cette fonctionnalité, le client (par exemple <em>Firefox</em>) doit savoir qu’il passe par un proxy (puisqu’il doit explicitement passer le nom de domaine au lieu de le résoudre localement), alors que notre <em>reverse tethering</em> doit être <strong>transparent</strong>.</p> <p>Mais tout n’est pas perdu. Certes, <em>OpenSSH</em> ne supporte pas UDP, mais ce n’est qu’une implémentation spécifique, nous pourrions en utiliser une autre. Malheureusement, <a href="http://stackoverflow.com/questions/41967217/why-does-socks5-require-to-relay-udp-over-udp">SOCKS5 relaie UDP sur UDP</a>, et les téléphones et l’ordinateur communiquent sur <em>adb</em> (grâce à <code class="highlighter-rouge">adb reverse</code>), qui ne supporte pas non plus la redirection de ports UDP.</p> <p>Peut-être que nous pourrions au moins relayer les requêtes DNS en les forçant à <a href="http://www.bortzmeyer.org/dns-over-tcp.html">utiliser TCP</a>, comme le fait <a href="https://linux.die.net/man/8/tsocks">tsocks</a> :</p> <blockquote> <p><strong>tsocks</strong> will normally not be able to send DNS queries through a SOCKS server since SOCKS V4 works on TCP and DNS normally uses UDP. Version 1.5 and up do however provide a method to force DNS lookups to use TCP, which then makes them proxyable.</p> </blockquote> <p>Mais finalement, SOCKS n’est plus une solution aussi attirante pour implémenter le <em>reverse tethering</em>.</p> <h3 id="gnirehtet">Gnirehtet</h3> <p>Par conséquent, j’ai développé à la fois le client et le serveur relais manuellement.</p> <p>Ce <a href="http://www.thegeekstuff.com/2014/06/android-vpn-service/">billet de blog</a> et différents projets open source (<a href="https://github.com/vvviperrr/SimpleRT"><code class="highlighter-rouge">SimpleRT</code></a>, <a href="https://github.com/google/vpn-reverse-tether"><code class="highlighter-rouge">vpn-reverse-tether</code></a>, <a href="https://github.com/hexene/LocalVPN"><code class="highlighter-rouge">LocalVPN</code></a> et <a href="https://android.googlesource.com/platform/development/+/master/samples/ToyVpn/"><code class="highlighter-rouge">ToyVpn</code></a>) m’ont beaucoup aidé à comprendre comment implémenter cette solution de <em>reverse tethering</em>.</p> <h2 id="conclusion">Conclusion</h2> <p><a href="https://github.com/Genymobile/gnirehtet"><em>Gnirehtet</em></a> permet aux téléphones et tablettes Android d’utiliser facilement la connection internet d’un ordinateur par USB, sans accès <em>root</em>. C’est très utile quand vous ne pouvez pas accéder au réseau par un point d’accès WiFi.</p> <p>J’espère qu’il pourra être utile à certains d’entre vous.</p> <p><em>Cet article est également disponible en anglais sur <a href="https://medium.com/@rom1v/gnirehtet-reverse-tethering-android-2afacdbdaec7">Medium</a>.</em></p> Serveur-client 2017-03-12T23:17:12+01:00 http://blog.rom1v.com/2017/03/serveur-client <p>L’objectif de ce billet est de parvenir à nous connecter à un serveur a priori inaccessible derrière un <a href="https://fr.wikipedia.org/wiki/Network_address_translation">NAT</a>.</p> <h2 id="client-serveur">Client-serveur</h2> <p>De nos jours, <a href="https://fr.wikipedia.org/wiki/Transmission_Control_Protocol">TCP</a> est toujours utilisé en mode <a href="https://fr.wikipedia.org/wiki/Client-serveur">client-serveur</a> :</p> <ul> <li>le <strong>serveur</strong> écoute <em>passivement</em> sur un <a href="https://fr.wikipedia.org/wiki/Port_%28logiciel%29">port</a> donné, en attente de la connexion d’un client ;</li> <li>le <strong>client</strong> initie <em>activement</em> une connexion vers un serveur.</li> </ul> <p>Une fois la connexion établie, cependant, le client et le serveur jouent exactement le même rôle au niveau de la communication. Par contre, très souvent, leur rôle applicatif dépend directement de celui qui a initié la connexion :</p> <ul> <li>c’est le <strong>client</strong> <a href="https://fr.wikipedia.org/wiki/Hypertext_Transfer_Protocol">HTTP</a> qui va envoyer une requête au <strong>serveur</strong> HTTP, pas l’inverse ;</li> <li>c’est le <strong>client</strong> <a href="https://fr.wikipedia.org/wiki/Secure_Shell">SSH</a> qui va ouvrir une session sur le <strong>serveur</strong> SSH…</li> </ul> <p class="center"><img src="/assets/serveur_client/ssh.png" alt="ssh" /></p> <p>Ce fonctionnement paraît tellement naturel que “<strong>client</strong>” désigne bien souvent à la fois celui qui initie la connexion et celui qui effectue des requêtes (au serveur), alors que “<strong>serveur</strong>” désigne aussi bien la partie en écoute que celle qui répondra aux requêtes (des clients).</p> <h2 id="puis-vint-le-nat">Puis vint le NAT…</h2> <p>Avec la <a href="https://fr.wikipedia.org/wiki/%C3%89puisement_des_adresses_IPv4">pénurie d’adresses IPv4</a>, le NAT s’est généralisé. Bien souvent, un accès internet ne fournit qu’une seule adresse <a href="https://fr.wikipedia.org/wiki/IPv4">IPv4</a>. Les différents ordinateurs partageant la même connexion ne sont alors pas accessibles directement depuis l’extérieur (il est nécessaire d’<a href="https://fr.wikipedia.org/wiki/Redirection_de_port">ouvrir des ports</a>).</p> <p>Ainsi, derrière un NAT sans ports ouverts, un <strong>serveur</strong> ne sera pas accessible publiquement. Par contre, un <strong>client</strong> pourra continuer à se connecter à n’importe quel serveur public.</p> <p class="center"><img src="/assets/serveur_client/ssh-nat.png" alt="ssh-nat" /></p> <h2 id="inversion-des-rles">Inversion des rôles</h2> <p>Il existe des situations pour lesquelles nous souhaitons qu’un logiciel joue le rôle de <strong>serveur</strong> au niveau applicatif, afin de répondre aux requêtes des clients, mais <strong>client</strong> au niveau de la communication, afin de passer les NATs sans difficultés.</p> <p>Par exemple, nous pouvons vouloir accéder, grâce à <a href="https://fr.wikipedia.org/wiki/Virtual_Network_Computing">VNC</a> ou SSH, à un ordinateur se trouvant derrière un NAT sur lequel, par hypothèse, nous n’avons pas la main. Dans ce cas, seul le <strong>serveur</strong> (au sens applicatif) aura la capacité d’ouvrir une connexion vers le <strong>client</strong>.</p> <h3 id="logiciel-ddi">Logiciel dédié</h3> <p>Il est possible d’utiliser un logiciel spécialement conçu pour gérer cette inversion des rôles. C’est le cas par exemple de <a href="https://doc.ubuntu-fr.org/gitso">gitso</a>, qui <em>inverse</em> le protocole VNC afin de simplifier l’aide de novices à distance.</p> <p>Cette solution a cependant l’inconvénient d’être très spécifique, nécessitant un développement supplémentaire pour chaque protocole.</p> <h3 id="redirection-de-port-distant-via-ssh">Redirection de port distant via SSH</h3> <p>SSH permet d’ouvrir un tunnel pour rediriger un port d’une machine distance vers une adresse quelconque.</p> <p>Par exemple, après avoir démarré la redirection :</p> <div class="highlighter-rouge"><pre class="codehilite"><code>ssh un_serveur_public -NR2222:localhost:22 </code></pre></div> <p>toutes les connexions arrivant sur <code class="highlighter-rouge">un_serveur_public:2222</code> seront redirigées de manière transparente vers <code class="highlighter-rouge">localhost:22</code> (sur la machine ayant initié le tunnel, donc).</p> <p><em>(Cela nécessite d’activer <code class="highlighter-rouge">GatewayPorts yes</code> dans <code class="highlighter-rouge">/etc/ssh/sshd_config</code> sur <code class="highlighter-rouge">un_serveur_public</code>.)</em></p> <p>De cette manière, un serveur SSH inaccessible derrière un NAT est rendu accessible à travers un tunnel en passant par une machine publique (<code class="highlighter-rouge">un_serveur_public</code>). Ainsi, il est possible de s’y connecter avec la commande :</p> <div class="highlighter-rouge"><pre class="codehilite"><code>ssh un_serveur_public -p2222 </code></pre></div> <p class="center"><img src="/assets/serveur_client/ssh-remote.png" alt="ssh-remote" /></p> <p>Cette stratégie fonctionne bien, mais elle nécessite que la machine qui souhaite exposer un serveur grâce à un tunnel possède un accès SSH sur <code class="highlighter-rouge">un_serveur_public</code>.</p> <p>Si l’on souhaite aider quelqu’un grâce à la prise de contrôle de sa machine à distance, il y a toutes les chances que cette personne n’ait pas d’accès SSH vers une machine publiquement accessible. Il est alors possible de lui <a href="https://tuxicoman.jesuislibre.net/2015/03/aide-a-une-noob-par-reverse-ssh.html">créer un compte restreint dédié</a> sur un serveur que l’on contrôle, mais c’est très intrusif, et il faut s’assurer de ne pas réduire la sécurité.</p> <p>Mais en fait, <strong>cette contrainte est superflue</strong>.</p> <h3 id="redirections-socat">Redirections SOCAT</h3> <p>La redirection de port distant nécessite des permissions car, outre le fait qu’elle est implémentée sur SSH, il serait déraisonnable d’autoriser n’importe qui à ouvrir une <a href="https://fr.wikipedia.org/wiki/Berkeley_sockets">socket</a> en écoute sur un port arbitraire d’une machine distante.</p> <p>Pour éviter ce problème, nous pouvons décomposer la redirection de port distant fourni par SSH en deux parties :</p> <ol> <li>l’ouverture de la connexion vers <code class="highlighter-rouge">un_serveur_public</code>, redirigée vers l’adresse <code class="highlighter-rouge">localhost:22</code> dans l’exemple précédent ;</li> <li>l’ouverture d’une socket en écoute sur un port (<code class="highlighter-rouge">2222</code>) de la machine distante, redirigée vers la première connexion.</li> </ol> <p>L’idée est de mettre en place le premier demi-tunnel sur la machine serveur, et le second demi-tunnel, nécessitant des permissions, sur la machine publique, contrôlée par le client.</p> <p>Pour cela, nous allons utiliser l’outil <code class="highlighter-rouge">socat</code>, qui permet de relayer les données entre deux sockets, quelque soit le rôle qu’elles aient joué lors de l’initialisation.</p> <h4 id="active-passive">Active-passive</h4> <p>Pour comprendre son utilisation, nous allons ouvrir grâce à <em>netcat</em> (<code class="highlighter-rouge">nc</code>) une socket TCP en écoute sur le port <code class="highlighter-rouge">5000</code> et nous y connecter :</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="c"># terminal 1</span> nc -l -p 5000 <span class="c"># terminal 2</span> nc localhost 5000</code></pre></figure> <p>Toute entrée validée par un retour à la ligne dans le terminal 1 s’affichera dans le terminal 2 (et vice-versa).</p> <p class="center"><img src="/assets/serveur_client/nc.png" alt="nc" /></p> <h4 id="passive-passive">Passive-passive</h4> <p>Démarrons maintenant dans deux terminaux différents une socket en écoute sur les ports <code class="highlighter-rouge">1111</code> et <code class="highlighter-rouge">2222</code> :</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="c"># terminal 1</span> nc -l -p 1111 <span class="c"># terminal 2</span> nc -l -p 2222</code></pre></figure> <p>Pour les mettre en communication avec <code class="highlighter-rouge">socat</code>, dans un 3e terminal :</p> <div class="highlighter-rouge"><pre class="codehilite"><code>socat tcp:localhost:1111 tcp:localhost:2222 </code></pre></div> <p class="center"><img src="/assets/serveur_client/socat-connect.png" alt="socat-connect" /></p> <h4 id="active-active">Active-active</h4> <p>Inversement, il est possible de mettre en communication deux sockets <em>actives</em> (sans compter sur leur <a href="http://linuxfr.org/users/benoar/journaux/syn-c-est-pour-synchronisation">synchronisation</a>). Pour cela, commençons par ouvrir le serveur relai :</p> <div class="highlighter-rouge"><pre class="codehilite"><code>socat tcp-listen:1111 tcp-listen:2222 </code></pre></div> <p>Puis connectons-y deux sockets :</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="c"># terminal 1</span> nc localhost 1111 <span class="c"># terminal 2</span> nc localhost 2222</code></pre></figure> <p class="center"><img src="/assets/serveur_client/socat-listen.png" alt="socat-connect" /></p> <h4 id="tunnel">Tunnel</h4> <p>Nous sommes maintenant prêts pour créer l’équivalent d’une redirection de port distant SSH grâce à deux <code class="highlighter-rouge">socat</code>s, qui vont permettre d’inverser la connexion uniquement sur la portion qui permet de traverser le NAT :</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="c"># sur un_serveur_public</span> socat tcp-listen:1234 tcp-listen:5678 <span class="c"># sur le serveur derrière le NAT</span> socat tcp:un_serveur_public:1234 tcp:localhost:22 <span class="c"># sur le client</span> ssh un_serveur_public -p5678</code></pre></figure> <p class="center"><img src="/assets/serveur_client/ssh-socat.png" alt="ssh-socat" /></p> SHAdow 2017-03-01T00:11:35+01:00 http://blog.rom1v.com/2017/03/shadow <p>Le 23 février, une équipe de chercheurs a <a href="https://security.googleblog.com/2017/02/announcing-first-sha1-collision.html">annoncé</a> avoir cassé <a href="https://en.wikipedia.org/wiki/SHA-1">SHA-1</a> <a href="http://shattered.io/">en pratique</a>, en générant une <a href="https://en.wikipedia.org/wiki/Collision_attack">collision</a>.</p> <p>À partir de leur travail, il est possible de produire de nouvelles paires de fichiers PDF arbitrairement différents qui auront la même signature SHA-1. Par exemple :</p> <p class="center"><a href="/assets/shadow/shadow1.pdf"><img src="/assets/shadow/tux.jpg" alt="shadow1-thumb" /></a> <a href="/assets/shadow/shadow2.pdf"><img src="/assets/shadow/troll.jpg" alt="shadow2-thumb" /></a></p> <div class="highlighter-rouge"><pre class="codehilite"><code>$ sha1sum shadow1.pdf shadow2.pdf fffe36a1d6f0a76a585af4f3838a4a46b6714f0c shadow1.pdf fffe36a1d6f0a76a585af4f3838a4a46b6714f0c shadow2.pdf $ sha256sum shadow1.pdf shadow2.pdf 502ccf8ecee10176d891fa4aeab295edec22b95141c2ae16d85f13b39879e37e shadow1.pdf 2546d272df653c5a99ef0914fa6ed43b336f309758ea873448154ebde90cdfe1 shadow2.pdf </code></pre></div> <p>J’explique dans ce billet le principe, et je fournis un outil qui produit, à partir de deux images JPEG, deux fichiers PDF différents de même SHA-1.</p> <h2 id="rutilisation">Réutilisation</h2> <p>En fabriquant leur collision, les <a href="https://shattered.io/static/shattered.pdf">auteurs</a> ont pris soin de la rendre réutilisable :</p> <blockquote> <p>Furthermore, the prefix of the colliding messages was carefully chosen so that they allow an attacker to forge two PDF documents with the same SHA-1 hash yet that display arbitrarily-chosen distinct visual content.</p> </blockquote> <p>Aujourd’hui, nous allons jouer aux attaquants.</p> <p>La réutilisation de la collision repose sur le fait qu’avec SHA-1, ajouter un suffixe identique à une collision existante produit encore une collision :</p> <div class="highlighter-rouge"><pre class="codehilite"><code>SHA1(A) == SHA1(B) ==&gt; SHA1(A|X) == SHA1(B|X) </code></pre></div> <p>(où <code class="highlighter-rouge">X|Y</code> est la concaténation de <code class="highlighter-rouge">X</code> et de <code class="highlighter-rouge">Y</code>)</p> <p>Autrement dit, vous prenez les fichiers qui produisent une collision, vous ajoutez les mêmes octets aux deux, vous obtenez le même SHA-1 :</p> <div class="highlighter-rouge"><pre class="codehilite"><code>$ { cat shattered-1.pdf; echo bonjour; } | sha1sum 4bfd4b804da3aa207b29d6f1300dde507988dc4b - $ { cat shattered-2.pdf; echo bonjour; } | sha1sum 4bfd4b804da3aa207b29d6f1300dde507988dc4b - </code></pre></div> <p>Il est donc trivial de créer de <em>nouvelles</em> collisions.</p> <p>Mais pour qu’elles aient un intérêt, encore faut-il :</p> <ol> <li>que les fichiers produits soient valides ;</li> <li>qu’une différence entre les fichiers soit visible par l’utilisateur.</li> </ol> <h2 id="diffrences">Différences</h2> <p>Les différences entre <code class="highlighter-rouge">shattered-1.pdf</code> et <code class="highlighter-rouge">shattered-2.pdf</code> se situent entre les adresses <code class="highlighter-rouge">0xc0</code> et <code class="highlighter-rouge">0x13f</code> :</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash">diff -U3 &lt;<span class="o">(</span>hd shattered-1.pdf<span class="o">)</span> &lt;<span class="o">(</span>hd shattered-2.pdf<span class="o">)</span></code></pre></figure> <figure class="highlight"><pre><code class="language-diff" data-lang="diff"><span class="gd">--- /dev/fd/63 2017-02-28 21:11:11.530135134 +0100 </span><span class="gi">+++ /dev/fd/62 2017-02-28 21:11:11.530135134 +0100 </span><span class="gu">@@ -10,14 +10,14 @@ </span> 00000090 72 65 61 6d 0a ff d8 ff fe 00 24 53 48 41 2d 31 |ream......$SHA-1| 000000a0 20 69 73 20 64 65 61 64 21 21 21 21 21 85 2f ec | is dead!!!!!./.| 000000b0 09 23 39 75 9c 39 b1 a1 c6 3c 4c 97 e1 ff fe 01 |.#9u.9...&lt;L.....| <span class="gd">-000000c0 73 46 dc 91 66 b6 7e 11 8f 02 9a b6 21 b2 56 0f |sF..f.~.....!.V.| -000000d0 f9 ca 67 cc a8 c7 f8 5b a8 4c 79 03 0c 2b 3d e2 |..g....[.Ly..+=.| -000000e0 18 f8 6d b3 a9 09 01 d5 df 45 c1 4f 26 fe df b3 |..m......E.O&amp;...| -000000f0 dc 38 e9 6a c2 2f e7 bd 72 8f 0e 45 bc e0 46 d2 |.8.j./..r..E..F.| -00000100 3c 57 0f eb 14 13 98 bb 55 2e f5 a0 a8 2b e3 31 |&lt;W......U....+.1| -00000110 fe a4 80 37 b8 b5 d7 1f 0e 33 2e df 93 ac 35 00 |...7.....3....5.| -00000120 eb 4d dc 0d ec c1 a8 64 79 0c 78 2c 76 21 56 60 |.M.....dy.x,v!V`| -00000130 dd 30 97 91 d0 6b d0 af 3f 98 cd a4 bc 46 29 b1 |.0...k..?....F).| </span><span class="gi">+000000c0 7f 46 dc 93 a6 b6 7e 01 3b 02 9a aa 1d b2 56 0b |.F....~.;.....V.| +000000d0 45 ca 67 d6 88 c7 f8 4b 8c 4c 79 1f e0 2b 3d f6 |E.g....K.Ly..+=.| +000000e0 14 f8 6d b1 69 09 01 c5 6b 45 c1 53 0a fe df b7 |..m.i...kE.S....| +000000f0 60 38 e9 72 72 2f e7 ad 72 8f 0e 49 04 e0 46 c2 |`8.rr/..r..I..F.| +00000100 30 57 0f e9 d4 13 98 ab e1 2e f5 bc 94 2b e3 35 |0W...........+.5| +00000110 42 a4 80 2d 98 b5 d7 0f 2a 33 2e c3 7f ac 35 14 |B..-....*3....5.| +00000120 e7 4d dc 0f 2c c1 a8 74 cd 0c 78 30 5a 21 56 64 |.M..,..t..x0Z!Vd| +00000130 61 30 97 89 60 6b d0 bf 3f 98 cd a8 04 46 29 a1 |a0..`k..?....F).| </span> 00000140 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| * 00000230 00 00 ff fe 00 fc 00 00 00 00 00 00 00 00 ff e0 |................|</code></pre></figure> <p>Nous devrons donc, quoi qu’il arrive, conserver les <code class="highlighter-rouge">0x140</code> (320) premiers octets : il s’agira forcément d’un fichier PDF.</p> <p>Pour analyser la structure sur un exemple minimal, je vous conseille l’exemple fourni à la dernière page du <a href="https://shattered.io/static/shattered.pdf">papier</a> (<code class="highlighter-rouge">good.pdf</code> et <code class="highlighter-rouge">bad.pdf</code>) :</p> <div class="highlighter-rouge"><pre class="codehilite"><code>&lt;&lt; -- base64 -d | tar xj QlpoOTFBWSZTWbL5V5MABl///////9Pv///v////+/////HDdK739/677r+W3/75rUNr4Aa/AAAAAAA CgEVTRtQDQAaA0AAyGmjTQGmgAAANGgAaMIAYgGgAABo0AAAAAADQAIAGQ0MgDIGmjQA0DRk0AaMQ0D QAGIANGgAAGRoNGQMRpo0GIGgBoGQAAIAGQ0MgDIGmjQA0DRk0AaMQ0DQAGIANGgAAGRoNGQMRpo0GI GgBoGQAAIAGQ0MgDIGmjQA0DRk0AaMQ0DQAGIANGgAAGRoNGQMRpo0GIGgBoGQAAIAGQ0MgDIGmjQA0 DRk0AaMQ0DQAGIANGgAAGRoNGQMRpo0GIGgBoGQAABVTUExEZATTICnkxNR+p6E09JppoyamjGhkm0a mmIyaekbUejU9JiGnqZqaaDxJ6m0JkZMQ2oaYmJ6gxqMyE2TUzJqfItligtJQJfYbl9Zy9QjQuB5mHQ RdSSXCCTHMgmSDYmdOoOmLTBJWiCpOhMQYpQlOYpJjn+wQUJSTCEpOMekaFaaNB6glCC0hKEJdHr6Bm UIHeph7YxS8WJYyGwgWnMTFJBDFSxSCCYljiEk7HZgJzJVDHJxMgY6tCEIIWgsKSlSZ0S8GckoIIF+5 51Ro4RCw260VCEpWJSlpWx/PMrLyVoyhWMAneDilBcUIeZ1j6NCkus0qUCWnahhk5KT4GpWMh3vm2nJ WjTL9Qg+84iExBJhNKpbV9tvEN265t3fu/TKkt4rXFTsV+NcupJXhOhOhJMQQktrqt4K8mSh9M2DAO2 X7uXGVL9YQxUtzQmS7uBndL7M6R7vX869VxqPurenSuHYNq1yTXOfNWLwgvKlRlFYqLCs6OChDp0HuT zCWscmGudLyqUuwVGG75nmyZhKpJyOE/pOZyHyrZxGM51DYIN+Jc8yVJgAykxKCEtW55MlfudLg3KG6 TtozalunXrroSxUpVLStWrWLFihMnVpkyZOrQnUrE6xq1CGtJlbAb5ShMbV1CZgqlKC0wCFCpMmUKSE kvFLaZC8wHOCVAlvzaJQ/T+XLb5Dh5TNM67p6KZ4e4ZSGyVENx2O27LzrTIteAreTkMZpW95GS0CEJY hMc4nToTJ0wQhKEyddaLb/rTqmgJSlkpnALxMhlNmuKEpkEkqhKUoEq3SoKUpIQcDgWlC0rYahMmLuP Q0fHqZaF4v2W8IoJ2EhMhYmSw7qql27WJS+G4rUplToFi2rSv0NSrVvDUpltQ8Lv6F8pXyxmFBSxiLS xglNC4uvXVKmAtusXy4YXGX1ixedEvXF1aX6t8adYnYCpC6rW1ZzdZYlCCxKEv8vpbqdSsXl8v1jCQv 0KEPxPTa/5rtWSF1dSgg4z4KjfIMNtgwWoWLEsRhKxsSA9ji7V5LRPwtumeQ8V57UtFSPIUmtQdOQfs eI2Ly1DMtk4Jl8n927w34zrWG6Pi4jzC82js/46Rt2IZoadWxOtMInS2xYmcu8mOw9PLYxQ4bdfFw3Z Pf/g2pzSwZDhGrZAl9lqky0W+yeanadC037xk496t0Dq3ctfmqmjgie8ln9k6Q0K1krb3dK9el4Xsu4 4LpGcenr2eQZ1s1IhOhnE56WnXf0BLWn9Xz15fMkzi4kpVxiTKGEpffErEEMvEeMZhUl6yD1SdeJYbx zGNM3ak2TAaglLZlDCVnoM6wV5DRrycwF8Zh/fRsdmhkMfAO1duwknrsFwrzePWeMwl107DWzymxdQw iSXx/lncnn75jL9mUzw2bUDqj20LTgtawxK2SlQg1CCZDQMgSpEqLjRMsykM9zbSIUqil0zNk7Nu+b5 J0DKZlhl9CtpGKgX5uyp0idoJ3we9bSrY7PupnUL5eWiDpV5mmnNUhOnYi8xyClkLbNmAXyoWk7GaVr M2umkbpqHDzDymiKjetgzTocWNsJ2E0zPcfht46J4ipaXGCfF7fuO0a70c82bvqo3HceIcRlshgu73s eO8BqlLIap2z5jTOY+T2ucCnBtAtva3aHdchJg9AJ5YdKHz7LoA3VKmeqxAlFyEnQLBxB2PAhAZ8Kvm uR6ELXws1Qr13Nd1i4nsp189jqvaNzt+0nEnIaniuP1+/UOZdyfoZh57ku8sYHKdvfW/jYSUks+0rK+ qtte+py8jWL9cOJ0fV8rrH/t+85/p1z2N67p/ZsZ3JmdyliL7lrNxZUlx0MVIl6PxXOUuGOeArW3vuE vJ2beoh7SGyZKHKbR2bBWO1d49JDIcVM6lQtu9UO8ec8pOnXmkcponBPLNM2CwZ9kNC/4ct6rQkPkQH McV/8XckU4UJCy+VeTA== -- </code></pre></div> <h2 id="aiguillage">Aiguillage</h2> <p>Notre objectif est que les quelques octets différents entre les deux fichiers PDF déterminent l’image à afficher.</p> <p>Il serait en théorie possible d’appliquer cet aiguillage au niveau de la structure du PDF, mais c’est en fait au niveau du JPEG qu’il sera implémenté :</p> <blockquote> <p>PDFs with the same MD5 hash have previously been constructed by Gebhardt et al. [<a href="http://csrc.nist.gov/groups/ST/hash/documents/Illies_NIST_05.pdf">12</a>] by exploiting so-called Indexed Color Tables and Color Transformation functions. However, this method is not effective for many common PDF viewers that lack support for these functionalities. Our PDFs rely on distinct parsings of JPEG images, similar to Gebhardt et al.’s TIFF technique [<a href="http://csrc.nist.gov/groups/ST/hash/documents/Illies_NIST_05.pdf">12</a>] and Albertini et al.’s JPEG technique [<a href="http://dx.doi.org/10.1007/978-3-319-13051-4_1">1</a>].</p> </blockquote> <h3 id="le-format-jpeg">Le format JPEG</h3> <p>Voici le strict nécessaire à savoir sur le format JPEG pour notre besoin.</p> <p>Une image est stockée entre les <a href="https://en.wikipedia.org/wiki/JPEG#Syntax_and_structure">marqueurs</a> <code class="highlighter-rouge">0xffd8</code> (<em>Start Of Image</em>) et <code class="highlighter-rouge">0xffd9</code> (<em>End Of Image</em>).</p> <p>Il est possible d’insérer autant de commentaires que l’on veut, grâce au marqueur <code class="highlighter-rouge">0xfffe</code> suivi de sa taille sur 2 octets (en <a href="https://en.wikipedia.org/wiki/Endianness">big-endian</a>). La taille compte le header de taille, mais pas le marqueur initial.</p> <p>Par exemple, si je veux insérer le commentaire <em>“Hello”</em> au tout début, mon fichier JPEG ressemblera à ceci :</p> <div class="highlighter-rouge"><pre class="codehilite"><code> ff d8 ff fe 00 07 48 65 6c 6c 6f … ff d9 [SOI] [EOI] [[COM] [LEN] H e l l o] &lt;-------------------&gt; 7 </code></pre></div> <p>Et c’est à peu près tout ce qu’il y a à savoir.</p> <h3 id="lastuce">L’astuce</h3> <p>Mettons en évidence la première différence entre les fichiers en collision.</p> <p>Dans le <strong>fichier 1</strong> :</p> <div class="highlighter-rouge"><pre class="codehilite"><code>000000b0 -- -- -- -- -- -- -- -- -- -- -- -- -- ff fe 01 000000c0 73 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- </code></pre></div> <p>Dans le <strong>fichier 2</strong> :</p> <div class="highlighter-rouge"><pre class="codehilite"><code>000000b0 -- -- -- -- -- -- -- -- -- -- -- -- -- ff fe 01 000000c0 7f -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- </code></pre></div> <p>Chacun définit un bloc de commentaires, mais pas de mêmes tailles. Dans le fichier 1, le début du prochain bloc sera à l’adresse <code class="highlighter-rouge">0x232</code> (<code class="highlighter-rouge">0xbf + 0x173</code>), alors que dans le fichier 2 il sera à l’adresse <code class="highlighter-rouge">0x23e</code> (<code class="highlighter-rouge">0xbf + 0x17f</code>).</p> <p>Nous avons donc trouvé notre aiguillage ; nous allons maintenant utiliser des commentaires JPEG pour cacher soit la première image, soit la seconde.</p> <p>Pour l’exploiter jusqu’au bout, il suffit de disposer les commentaires astucieusement pour que les deux versions représentent des images parfaitement valides.</p> <p>Nous allons donc commencer en <code class="highlighter-rouge">0x232</code> un bloc de commentaires, ayant une taille permettant de recouvrir l’intégralité de l’image que nous allons stocker en <code class="highlighter-rouge">0x23e</code>. Et inversement, nous devons démarrer un commentaire à la fin de l’image stockée en <code class="highlighter-rouge">0x23e</code> pour cacher la deuxième image.</p> <p>Comparons sur le résultat ce qu’observe un <em>parseur</em> qui parcourt chacun des fichiers.</p> <p><em>(<code class="highlighter-rouge">GG</code> et <code class="highlighter-rouge">HH</code> sont les deux images à stocker. <code class="highlighter-rouge">J1</code> et <code class="highlighter-rouge">J2</code> sont les longueurs des sauts pour enjamber chacune des images.)</em></p> <p>Le <strong>fichier 1</strong> est <em>parsé</em> ainsi :</p> <pre><code> 00000090 -- -- -- -- -- <strong>ff d8</strong> -- -- -- -- -- -- -- -- -- … 000000b0 -- -- -- -- -- -- -- -- -- -- -- -- -- <span style="color: blue;">ff fe 01</span> 000000c0 <span style="color: blue;">73 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --</span> … 00000230 <span style="color: blue;">-- --</span> <span style="color: green;">ff fe J1 J1 -- -- -- -- -- -- -- -- GG GG</span> 00000240 <span style="color: green;">GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG</span> … i <span style="color: green;">GG GG GG GG GG GG ff fe J2 J2</span> <span style="color: red;">HH HH HH HH HH HH</span> i+0x10 <span style="color: red;">HH HH HH HH HH HH HH HH HH HH HH HH HH HH HH HH</span> … j <span style="color: red;">HH HH</span> <strong>ff d9</strong> -- -- -- -- -- -- -- -- -- -- -- -- </code></pre> <p>Le <strong>fichier 2</strong> est <em>parsé</em> différemment :</p> <pre><code> 00000090 -- -- -- -- -- <strong>ff d8</strong> -- -- -- -- -- -- -- -- -- … 000000b0 -- -- -- -- -- -- -- -- -- -- -- -- -- <span style="color: blue;">ff fe 01</span> 000000c0 <span style="color: blue;">7f -- -- -- -- -- -- -- -- -- -- -- -- -- -- --</span> … 00000230 <span style="color: blue;">-- -- ff fe J1 J1 -- -- -- -- -- -- -- --</span> <span style="color: red;">GG GG</span> 00000240 <span style="color: red;">GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG</span> … i <span style="color: red;">GG GG GG GG GG GG</span> <span style="color: green;">ff fe J2 J2 HH HH HH HH HH HH</span> i+0x10 <span style="color: green;">HH HH HH HH HH HH HH HH HH HH HH HH HH HH HH HH</span> … j <span style="color: green;">HH HH</span> <strong>ff d9</strong> -- -- -- -- -- -- -- -- -- -- -- -- </code></pre> <p>Les structures JPEG sont donc valides dans les deux fichiers. L’image affichée dépendra de l’octet stocké à l’adresse <code class="highlighter-rouge">0xc0</code>, valant soit <code class="highlighter-rouge">0x73</code>, soit <code class="highlighter-rouge">0x7f</code>.</p> <p>Maintenant, il nous reste à rendre notre PDF valide.</p> <h2 id="pdf">PDF</h2> <p>Le header participant à la collision SHA-1 (donc figé) définit des configurations dans des <em>sections</em> séparées (donc non figées) :</p> <div class="highlighter-rouge"><pre class="codehilite"><code>00000000 25 50 44 46 2d 31 2e 33 0a 25 e2 e3 cf d3 0a 0a |%PDF-1.3.%......| 00000010 0a 31 20 30 20 6f 62 6a 0a 3c 3c 2f 57 69 64 74 |.1 0 obj.&lt;&lt;/Widt| 00000020 68 20 32 20 30 20 52 2f 48 65 69 67 68 74 20 33 |h 2 0 R/Height 3| 00000030 20 30 20 52 2f 54 79 70 65 20 34 20 30 20 52 2f | 0 R/Type 4 0 R/| 00000040 53 75 62 74 79 70 65 20 35 20 30 20 52 2f 46 69 |Subtype 5 0 R/Fi| 00000050 6c 74 65 72 20 36 20 30 20 52 2f 43 6f 6c 6f 72 |lter 6 0 R/Color| 00000060 53 70 61 63 65 20 37 20 30 20 52 2f 4c 65 6e 67 |Space 7 0 R/Leng| 00000070 74 68 20 38 20 30 20 52 2f 42 69 74 73 50 65 72 |th 8 0 R/BitsPer| 00000080 43 6f 6d 70 6f 6e 65 6e 74 20 38 3e 3e 0a 73 74 |Component 8&gt;&gt;.st| </code></pre></div> <p>Ainsi, la largeur (<code class="highlighter-rouge">Width</code>) est définie dans l’objet <code class="highlighter-rouge">2</code>, la hauteur (<code class="highlighter-rouge">Height</code>) dans l’objet <code class="highlighter-rouge">3</code>, etc.</p> <p>Ces objets sont à définir à la suite des fichiers JPEG embarqués. Pour comprendre leur format, le plus simple est de lire le fichier <code class="highlighter-rouge">good.pdf</code> que je recommandais plus haut :</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash">tail -c+<span class="k">$((</span><span class="m">0</span>x746<span class="k">))</span> good.pdf</code></pre></figure> <p>On y trouve la définition des objets (entre autres les dimensions de l’image) :</p> <div class="highlighter-rouge"><pre class="codehilite"><code>2 0 obj 8 endobj 3 0 obj 8 endobj 4 0 obj /XObject endobj 5 0 obj /Image endobj 6 0 obj /DCTDecode endobj 7 0 obj /DeviceRGB endobj 8 0 obj 1693 endobj 9 0 obj &lt;&lt; /Type /Catalog /Pages 10 0 R &gt;&gt; endobj 10 0 obj &lt;&lt; /Type /Pages /Count 1 /Kids [11 0 R] &gt;&gt; endobj 11 0 obj &lt;&lt; /Type /Page /Parent 10 0 R /MediaBox [0 0 8 8] /CropBox [0 0 8 8] /Contents 12 0 R /Resources &lt;&lt; /XObject &lt;&lt;/Im0 1 0 R&gt;&gt; &gt;&gt; &gt;&gt; endobj 12 0 obj &lt;&lt;/Length 30&gt;&gt; stream q 8 0 0 8 0 0 cm /Im0 Do Q endstream endobj </code></pre></div> <p>Ensuite vient la table de références croisées ; elle indique l’<em>offset</em> de chacun des objets définis, dans l’ordre :</p> <div class="highlighter-rouge"><pre class="codehilite"><code>xref 0 13 0000000000 65535 f 0000000017 00000 n 0000001861 00000 n 0000001879 00000 n 0000001897 00000 n 0000001922 00000 n 0000001945 00000 n 0000001972 00000 n 0000001999 00000 n 0000002020 00000 n 0000002076 00000 n 0000002142 00000 n 0000002309 00000 n </code></pre></div> <p><em>À chaque ajout ou suppression de caractères dans la définition des objets, cette table doit être mise à jour.</em></p> <p>Le fichier se termine par un <em>trailer</em>, contenant l’<em>offset</em> de la table de références :</p> <div class="highlighter-rouge"><pre class="codehilite"><code>trailer &lt;&lt; /Root 9 0 R /Size 13&gt;&gt; startxref 2391 %%EOF </code></pre></div> <p>Ces <em>offsets</em> sont un peu fastidieux à modifier à la main, mais ça fonctionne.</p> <h2 id="shadow">SHAdow</h2> <p>J’ai donc écrit un petit outil qui applique toutes ces opérations automatiquement.</p> <div class="highlighter-rouge"><pre class="codehilite"><code>git clone http://git.rom1v.com/shadow.git </code></pre></div> <p>(ou sur <a href="https://github.com/rom1v/shadow">github</a>)</p> <p>Il prend en entrée deux images JPEG (de moins de 64K, puisque la taille d’un commentaire est codé sur 2 octets), ainsi que leurs dimensions (afin d’éviter d’utiliser des dépendances pour les extraire) :</p> <div class="highlighter-rouge"><pre class="codehilite"><code>./shadow.py img1.jpg img2.jpg 200 200 </code></pre></div> <p>Il génère deux fichiers dans le répertoire courant, <code class="highlighter-rouge">shadow1.pdf</code> et <code class="highlighter-rouge">shadow2.pdf</code>.</p> <p>Il ne reste qu’à vérifier qu’ils ont bien le même SHA-1 :</p> <div class="highlighter-rouge"><pre class="codehilite"><code>sha1sum shadow1.pdf shadow2.pdf </code></pre></div> C++ sans *pointeurs 2017-01-12T19:33:14+01:00 http://blog.rom1v.com/2017/01/cpp-sans-pointeurs <p>Les <a href="https://fr.wikipedia.org/wiki/Pointeur_%28programmation%29">pointeurs</a> sont utilisés plus souvent que nécessaire en <a href="https://fr.wikipedia.org/wiki/C%2B%2B">C++</a>.</p> <p>Je voudrais présenter ici comment caractériser les utilisations abusives et par quoi les remplacer.</p> <h2 id="objectifs">Objectifs</h2> <p>La décision d’utiliser des pointeurs dépend en grande partie de l’<a href="https://fr.wikipedia.org/wiki/Interface_de_programmation">API</a> des objets utilisés.</p> <p><em>API est à comprendre dans un sens très large : je considère que des classes utilisées dans une autre partie d’une même application exposent une API.</em></p> <p>L’objectif est donc de concevoir des API de manière à ce que leur utilisation ne nécessite pas de manipuler de pointeurs, ni même si possible de <em>smart pointers</em>.</p> <p>Cela peut paraître surprenant, mais c’est en fait ainsi que vous utilisez les classes de la <a href="https://fr.wikipedia.org/wiki/Standard_Template_Library">STL</a> ou de <a href="https://fr.wikipedia.org/wiki/Qt">Qt</a> : vos méthodes ne retournent jamais un <em>raw pointer</em> ni un <em>smart pointer</em> vers une <em>string</em> nouvellement créée.</p> <p>De manière générale, vous n’écririez pas ceci :</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="c1">// STL version </span><span class="n">string</span> <span class="o">*</span><span class="nf">getName</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="k">new</span> <span class="n">string</span><span class="p">(</span><span class="s">"my name"</span><span class="p">);</span> <span class="p">}</span> <span class="c1">// Qt version </span><span class="n">QString</span> <span class="o">*</span><span class="nf">getName</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="k">new</span> <span class="n">QString</span><span class="p">(</span><span class="s">"my name"</span><span class="p">);</span> <span class="p">}</span></code></pre></figure> <p>ni ceci :</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="c1">// STL version </span><span class="n">shared_ptr</span><span class="o">&lt;</span><span class="n">string</span><span class="o">&gt;</span> <span class="n">getName</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="n">make_shared</span><span class="o">&lt;</span><span class="n">string</span><span class="o">&gt;</span><span class="p">(</span><span class="s">"my name"</span><span class="p">);</span> <span class="p">}</span> <span class="c1">// Qt version </span><span class="n">QSharedPointer</span><span class="o">&lt;</span><span class="n">QString</span><span class="o">&gt;</span> <span class="n">getName</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="n">QSharedPointer</span><span class="o">&lt;</span><span class="n">QString</span><span class="o">&gt;::</span><span class="n">create</span><span class="p">(</span><span class="s">"my name"</span><span class="p">);</span> <span class="p">}</span></code></pre></figure> <p>À la place, vous écririez sûrement :</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="c1">// STL version </span><span class="n">string</span> <span class="nf">getName</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="s">"my name"</span><span class="p">;</span> <span class="p">}</span> <span class="c1">// Qt version </span><span class="n">QString</span> <span class="nf">getName</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="s">"my name"</span><span class="p">;</span> <span class="p">}</span></code></pre></figure> <p>Notre objectif est d’écrire des classes qui s’utiliseront de la même manière.</p> <h2 id="ownership">Ownership</h2> <p>Il faut distinguer deux types de <em>raw pointers</em> :</p> <ol> <li>ceux qui détiennent l’objet pointé (<strong><em>owning</em></strong>), qui devront être libérés ;</li> <li>ceux qui ne le détiennent pas (<strong><em>non-owning</em></strong>).</li> </ol> <p>Le plus simple est de les comparer sur un exemple.</p> <h3 id="owning">Owning</h3> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="n">Info</span> <span class="o">*</span><span class="nf">getInfo</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="k">new</span> <span class="n">Info</span><span class="p">(</span><span class="cm">/* … */</span><span class="p">);</span> <span class="p">}</span> <span class="kt">void</span> <span class="nf">doSomething</span><span class="p">()</span> <span class="p">{</span> <span class="n">Info</span> <span class="o">*</span><span class="n">info</span> <span class="o">=</span> <span class="n">getInfo</span><span class="p">();</span> <span class="c1">// info must be deleted </span><span class="p">}</span></code></pre></figure> <p>Ici, nous avons la responsabilité de supprimer <code class="highlighter-rouge">info</code> au bon moment.</p> <p><strong>C’est ce type de pointeurs dont nous voulons nous débarrasser.</strong></p> <h3 id="non-owning">Non-owning</h3> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="kt">void</span> <span class="nf">writeDataTo</span><span class="p">(</span><span class="n">QBuffer</span> <span class="o">*</span><span class="n">buffer</span><span class="p">)</span> <span class="p">{</span> <span class="n">buffer</span><span class="o">-&gt;</span><span class="n">write</span><span class="p">(</span><span class="s">"c++"</span><span class="p">);</span> <span class="p">}</span> <span class="kt">void</span> <span class="nf">doSomething</span><span class="p">()</span> <span class="p">{</span> <span class="n">QBuffer</span> <span class="n">buffer</span><span class="p">;</span> <span class="n">writeDataTo</span><span class="p">(</span><span class="o">&amp;</span><span class="n">buffer</span><span class="p">);</span> <span class="p">}</span></code></pre></figure> <p>Ici, le pointeur permet juste de passer l’adresse de l’objet, mais la méthode <code class="highlighter-rouge">writeDataTo(…)</code> ne doit pas gérer sa durée de vie : elle ne le <em>détient</em> donc pas.</p> <p><strong>Cet usage est tout-à-fait légitime, nous souhaitons le conserver.</strong></p> <p>Pour savoir si un pointeur est <em>owning</em> ou non, il suffit de se poser la question suivante : est-ce que lui affecter <code class="highlighter-rouge">nullptr</code> provoquerait une <a href="https://en.wikipedia.org/wiki/Memory_leak">fuite mémoire</a> ?</p> <h2 id="pourquoi">Pourquoi ?</h2> <p>Voici quelques exemples illustrant pourquoi nous voulons éviter les <em>owning raw pointers</em>.</p> <h3 id="fuite-mmoire">Fuite mémoire</h3> <p>Il est facile d’oublier de supprimer un pointeur dans des cas particuliers.</p> <p>Par exemple :</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="kt">bool</span> <span class="nf">parse</span><span class="p">()</span> <span class="p">{</span> <span class="n">Parser</span> <span class="o">*</span><span class="n">parser</span> <span class="o">=</span> <span class="n">createParser</span><span class="p">();</span> <span class="n">QFile</span> <span class="n">file</span><span class="p">(</span><span class="s">"file.txt"</span><span class="p">);</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">file</span><span class="p">.</span><span class="n">open</span><span class="p">(</span><span class="n">QIODevice</span><span class="o">::</span><span class="n">ReadOnly</span><span class="p">))</span> <span class="k">return</span> <span class="nb">false</span><span class="p">;</span> <span class="kt">bool</span> <span class="n">result</span> <span class="o">=</span> <span class="n">parser</span><span class="o">-&gt;</span><span class="n">parse</span><span class="p">(</span><span class="o">&amp;</span><span class="n">file</span><span class="p">);</span> <span class="k">delete</span> <span class="n">parser</span><span class="p">;</span> <span class="k">return</span> <span class="n">result</span><span class="p">;</span> <span class="c1">// parser leaked if open failed </span><span class="p">}</span></code></pre></figure> <p>Ici, si l’ouverture du fichier a échoué, <code class="highlighter-rouge">parser</code> ne sera jamais libéré.</p> <p>L’exemple suivant est encore plus significatif :</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="n">Result</span> <span class="o">*</span><span class="nf">execute</span><span class="p">()</span> <span class="p">{</span> <span class="c1">// … </span> <span class="k">return</span> <span class="k">new</span> <span class="n">Result</span><span class="p">(</span><span class="cm">/* … */</span><span class="p">);</span> <span class="p">}</span> <span class="kt">void</span> <span class="nf">doWork</span><span class="p">()</span> <span class="p">{</span> <span class="n">execute</span><span class="p">();</span> <span class="c1">// result leaked </span><span class="p">}</span></code></pre></figure> <p>Appeler une méthode sans s’occuper du résultat peut provoquer des fuites mémoires.</p> <h3 id="double-suppression">Double suppression</h3> <p>Il est également possible, par inattention, de supprimer plusieurs fois le même pointeur (ce qui entraîne un <a href="/2014/10/comportement-indefini-et-optimisation/"><em>undefined behavior</em></a>).</p> <p>Par exemple, si <code class="highlighter-rouge">device</code> fait partie de la liste <code class="highlighter-rouge">devices</code>, ce code le supprime deux fois :</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="k">delete</span> <span class="n">device</span><span class="p">;</span> <span class="n">qDeleteAll</span><span class="p">(</span><span class="n">devices</span><span class="p">);</span> <span class="o">//</span> <span class="n">device</span> <span class="n">is</span> <span class="n">deleted</span> <span class="n">twice</span></code></pre></figure> <h3 id="utilisation-aprs-suppression">Utilisation après suppression</h3> <p>L’utilisation d’un pointeur après sa suppression est également indéfinie.</p> <p>Je vais prendre un exemple réel en Qt.</p> <p>Supposons qu’une classe <code class="highlighter-rouge">DeviceMonitor</code> surveille le branchement de périphériques, et crée pour chacun un objet <code class="highlighter-rouge">Device</code>.</p> <p><a id="suppression-complexe"></a> Lorsqu’un périphérique est débranché, un <a href="http://doc.qt.io/qt-5/signalsandslots.html">signal Qt</a> provoque l’exécution du <em>slot</em> <code class="highlighter-rouge">DeviceMonitor::onDeviceLeft(Device *)</code>. Nous voulons alors signaler au reste de l’application que le device est parti (<em>signal</em> <code class="highlighter-rouge">DeviceMonitor::deviceLeft(Device *)</code>), puis supprimer l’object <code class="highlighter-rouge">device</code> correspondant :</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="kt">void</span> <span class="n">DeviceMonitor</span><span class="o">::</span><span class="n">onDeviceLeft</span><span class="p">(</span><span class="n">Device</span> <span class="o">*</span><span class="n">device</span><span class="p">)</span> <span class="p">{</span> <span class="n">emit</span> <span class="n">deviceLeft</span><span class="p">(</span><span class="n">device</span><span class="p">);</span> <span class="k">delete</span> <span class="n">device</span><span class="p">;</span> <span class="c1">// slots may use the device after its deletion </span> <span class="c1">// device-&gt;deleteLater() not sufficient </span><span class="p">}</span></code></pre></figure> <p>Mais c’est loin d’être trivial.</p> <p>Si nous le supprimons immédiatement comme ceci, et qu’un <em>slot</em> est branché à <code class="highlighter-rouge">DeviceMonitor::deviceLeft(Device *)</code> en <a href="http://doc.qt.io/qt-5/qt.html#ConnectionType-enum"><code class="highlighter-rouge">Qt::QueuedConnection</code></a>, alors il est possible que le pointeur soit déjà supprimé quand ce <em>slot</em> sera exécuté.</p> <p>Un proverbe dit que quand ça crashe avec un <code class="highlighter-rouge">delete</code>, <em>“il faut appeller <a href="http://doc.qt.io/qt-5/qobject.html#deleteLater"><code class="highlighter-rouge">deleteLater()</code></a> pour corriger le problème”</em> :</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="n">device</span><span class="o">-&gt;</span><span class="n">deleteLater</span><span class="p">();</span></code></pre></figure> <p>Mais malheureusement, ici, c’est faux : si le <em>slot</em> branché au <em>signal</em> <code class="highlighter-rouge">DeviceMonitor::deviceLeft(Device *)</code> est associé à un <a href="http://doc.qt.io/qt-5/qobject.html"><code class="highlighter-rouge">QObject</code></a> vivant dans un autre <a href="http://doc.qt.io/qt-5/qobject.html#thread-affinity">thread</a>, rien ne garantit que son exécution aura lieu avant la suppression du pointeur.</p> <p>L’utilisation des <em>owning raw pointers</em> n’est donc pas seulement vulnérable aux erreurs d’inattention (comme dans les exemples précédents) : dans des cas plus complexes, il devient <strong>difficile de déterminer quand supprimer le pointeur</strong>.</p> <h3 id="responsabilit">Responsabilité</h3> <p>De manière plus générale, lorsque nous avons un pointeur, nous ne savons pas forcément qui a la responsabilité de le supprimer, ni comment le supprimer :</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="n">Data</span> <span class="o">*</span><span class="n">data</span> <span class="o">=</span> <span class="n">getSomeData</span><span class="p">();</span> <span class="k">delete</span> <span class="n">data</span><span class="p">;</span> <span class="c1">// ? </span><span class="n">free</span><span class="p">(</span><span class="n">data</span><span class="p">);</span> <span class="c1">// ? </span><span class="n">custom_deleter</span><span class="p">(</span><span class="n">data</span><span class="p">);</span> <span class="o">//</span> <span class="o">?</span></code></pre></figure> <p><em>Qt fournit un mécanisme pour supprimer automatiquement les <code class="highlighter-rouge">QObject *</code> quand leur parent est détruit. Cependant, cette fonctionnalité ne s’applique qu’aux <a href="https://fr.wikipedia.org/wiki/Composition_%28programmation%29">relations de composition</a>.</em></p> <p>Résumons les inconvénients des <em>owning raw pointeurs</em> :</p> <ul> <li>la gestion mémoire est manuelle ;</li> <li>leur utilisation est propice aux erreurs ;</li> <li>la responsabilité de suppression n’est pas apparente ;</li> <li>déterminer quand supprimer le pointeur peut être difficile.</li> </ul> <h2 id="valeurs">Valeurs</h2> <p>Laissons de côté les pointeurs quelques instants pour observer ce qu’il se passe avec de simples <em>valeurs</em> (des <em>objets</em> plutôt que des <em>pointeurs vers des objets</em>) :</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="k">struct</span> <span class="n">Vector</span> <span class="p">{</span> <span class="kt">int</span> <span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">z</span><span class="p">;</span> <span class="p">};</span> <span class="n">Vector</span> <span class="nf">transform</span><span class="p">(</span><span class="k">const</span> <span class="n">Vector</span> <span class="o">&amp;</span><span class="n">v</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="p">{</span> <span class="o">-</span><span class="n">v</span><span class="p">.</span><span class="n">x</span><span class="p">,</span> <span class="n">v</span><span class="p">.</span><span class="n">z</span><span class="p">,</span> <span class="n">v</span><span class="p">.</span><span class="n">y</span> <span class="p">};</span> <span class="p">}</span> <span class="kt">void</span> <span class="nf">compute</span><span class="p">()</span> <span class="p">{</span> <span class="n">Vector</span> <span class="n">vector</span> <span class="o">=</span> <span class="n">transform</span><span class="p">({</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span> <span class="p">});</span> <span class="n">emit</span> <span class="n">finished</span><span class="p">(</span><span class="n">transform</span><span class="p">(</span><span class="n">vector</span><span class="p">));</span> <span class="p">}</span></code></pre></figure> <p>C’est plus simple : la gestion mémoire est automatique, et le code est plus sûr. Par exemple, les fuites mémoire et les double suppressions sont impossibles.</p> <p><strong>Ce sont des avantages dont nous souhaiterions bénéficier également pour les pointeurs.</strong></p> <h3 id="privilgier-les-valeurs">Privilégier les valeurs</h3> <p>Dans les cas où les pointeurs sont utilisés uniquement pour éviter de retourner des copies (et non pour partager des objets), il est préférable de <strong>retourner les objets par valeur</strong> à la place.</p> <p>Par exemple, si vous avez une classe :</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="k">struct</span> <span class="n">Result</span> <span class="p">{</span> <span class="n">QString</span> <span class="n">message</span><span class="p">;</span> <span class="kt">int</span> <span class="n">code</span><span class="p">;</span> <span class="p">};</span></code></pre></figure> <p>Évitez :</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="n">Result</span> <span class="o">*</span><span class="nf">execute</span><span class="p">()</span> <span class="p">{</span> <span class="c1">// … </span> <span class="k">return</span> <span class="k">new</span> <span class="n">Result</span> <span class="p">{</span> <span class="n">message</span><span class="p">,</span> <span class="n">code</span> <span class="p">};</span> <span class="p">}</span></code></pre></figure> <p>Préférez :</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="n">Result</span> <span class="nf">execute</span><span class="p">()</span> <span class="p">{</span> <span class="c1">// … </span> <span class="k">return</span> <span class="p">{</span> <span class="n">message</span><span class="p">,</span> <span class="n">code</span> <span class="p">};</span> <span class="p">}</span></code></pre></figure> <p>Certes, dans certains cas, il est moins efficace de passer un objet par valeur qu’à travers un pointeur (car il faut le copier).</p> <p>Mais cette inefficacité est à relativiser.</p> <p>D’abord parce que dans certains cas <em>(quand l’objet est copié à partir d’une <a href="http://thbecker.net/articles/rvalue_references/section_01.html">rvalue reference</a>)</em>, la copie sera remplacée par un <a href="http://www.cprogramming.com/c++11/rvalue-references-and-move-semantics-in-c++11.html"><em>move</em></a>. Le <em>move</em> d’un <a href="http://en.cppreference.com/w/cpp/container/vector"><code class="highlighter-rouge">vector</code></a> par exemple n’entraîne aucune copie (ni <em>move</em>) de ses éléments.</p> <p>Ensuite parce que les compilateurs optimisent le retour par valeur (<a href="https://en.wikipedia.org/wiki/Return_value_optimization">RVO</a>), ce qui fait qu’en réalité dans les exemples ci-dessus, aucun <code class="highlighter-rouge">Result</code> ni <code class="highlighter-rouge">Vector</code> n’est jamais copié ni <em>mové</em> : ils sont directement créés à l’endroit où ils sont affectés <em>(sauf si vous compilez avec le paramètre <code class="highlighter-rouge">-fno-elide-constructors</code>)</em>.</p> <p>Mais évidemment, il y a des cas où nous ne pouvons pas simplement remplacer un <em>pointeur</em> par une <em>valeur</em>, par exemple quand un même objet doit être partagé entre différentes parties d’un programme.</p> <p><strong>Nous voudrions les avantages des <em>valeurs</em> également pour ces cas-là.</strong> C’est l’objectif de la suite du billet.</p> <h2 id="idiomes-c">Idiomes C++</h2> <p>Pour y parvenir, nous avons besoin de faire un détour par quelques <em>idiomes</em> couramment utilisés en C++.</p> <p>Ils ont souvent un nom étrange. Par exemple :</p> <ul> <li><a href="https://en.wikipedia.org/wiki/Resource_acquisition_is_initialization">RAII</a> <em>(Resource Acquisition Is Initialization)</em></li> <li><a href="https://en.wikipedia.org/wiki/Opaque_pointer">PIMPL</a> <em>(Pointer to IMPLementation)</em></li> <li><a href="https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern">CRTP</a> <em>(Curiously Recurring Template Pattern)</em></li> <li><a href="https://en.wikipedia.org/wiki/Substitution_failure_is_not_an_error">SFINAE</a> <em>(Substitution Failure Is Not An Error)</em></li> <li><a href="https://en.wikipedia.org/wiki/Immediately-invoked_function_expression">IIFE</a> <em>(Immediately-Invoked Function Expression)</em></li> </ul> <p>Nous allons étudier les deux premiers.</p> <h3 id="raii">RAII</h3> <p>Prenons un exemple simple :</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="kt">bool</span> <span class="nf">submit</span><span class="p">()</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">validate</span><span class="p">())</span> <span class="k">return</span> <span class="nb">false</span><span class="p">;</span> <span class="k">return</span> <span class="n">something</span><span class="p">();</span> <span class="p">}</span></code></pre></figure> <p>Nous souhaitons rendre cette méthode <a href="https://fr.wikipedia.org/wiki/Thread_safety">thread-safe</a> grâce à un <a href="https://fr.wikipedia.org/wiki/Exclusion_mutuelle">mutex</a> (<a href="http://en.cppreference.com/w/cpp/thread/mutex"><code class="highlighter-rouge">std::mutex</code></a> en STL ou <a href="http://doc.qt.io/qt-5/qmutex.html"><code class="highlighter-rouge">QMutex</code></a> en Qt).</p> <p>Supposons que <code class="highlighter-rouge">validate()</code> et <code class="highlighter-rouge">something()</code> puissent lever une <a href="http://en.cppreference.com/w/cpp/language/exceptions">exception</a>.</p> <p>Le <em>mutex</em> doit être déverrouillé à la fin de l’exécution de la méthode. Le problème, c’est que cela peut se produire à différents endroits, donc nous devons gérer tous les cas :</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="kt">bool</span> <span class="nf">submit</span><span class="p">()</span> <span class="p">{</span> <span class="n">mutex</span><span class="p">.</span><span class="n">lock</span><span class="p">();</span> <span class="k">try</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">validate</span><span class="p">())</span> <span class="p">{</span> <span class="n">mutex</span><span class="p">.</span><span class="n">unlock</span><span class="p">();</span> <span class="k">return</span> <span class="nb">false</span><span class="p">;</span> <span class="p">}</span> <span class="kt">bool</span> <span class="n">result</span> <span class="o">=</span> <span class="n">something</span><span class="p">();</span> <span class="n">mutex</span><span class="p">.</span><span class="n">unlock</span><span class="p">();</span> <span class="k">return</span> <span class="n">result</span><span class="p">;</span> <span class="p">}</span> <span class="k">catch</span> <span class="p">(...)</span> <span class="p">{</span> <span class="n">mutex</span><span class="p">.</span><span class="n">unlock</span><span class="p">();</span> <span class="k">throw</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span></code></pre></figure> <p>Le code est beaucoup plus complexe et propice aux erreurs.</p> <p>Avec des classes utilisant RAII (<a href="http://en.cppreference.com/w/cpp/thread/lock_guard"><code class="highlighter-rouge">std::lock_guard</code></a> en STL ou <a href="http://doc.qt.io/qt-5/qmutexlocker.html"><code class="highlighter-rouge">QMutexLocker</code></a> en Qt), c’est beaucoup plus simple :</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="kt">bool</span> <span class="nf">submit</span><span class="p">()</span> <span class="p">{</span> <span class="n">QMutexLocker</span> <span class="n">locker</span><span class="p">(</span><span class="o">&amp;</span><span class="n">mutex</span><span class="p">);</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">validate</span><span class="p">())</span> <span class="k">return</span> <span class="nb">false</span><span class="p">;</span> <span class="k">return</span> <span class="n">something</span><span class="p">();</span> <span class="p">}</span></code></pre></figure> <p>En ajoutant une seule ligne, la méthode est devenue <em>thread-safe</em>.</p> <p>Cette technique consiste à utiliser le cycle de vie d’un objet pour acquérir une ressource dans le constructeur (ici verrouiller le <em>mutex</em>) et la relâcher dans le destructeur (ici le déverrouiller).</p> <p>Voici une implémentation simplifiée possible de <a href="http://doc.qt.io/qt-5/qmutexlocker.html"><code class="highlighter-rouge">QMutexLocker</code></a> :</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="k">class</span> <span class="nc">QMutexLocker</span> <span class="p">{</span> <span class="n">QMutex</span> <span class="o">*</span><span class="n">mutex</span><span class="p">;</span> <span class="k">public</span><span class="o">:</span> <span class="k">explicit</span> <span class="n">QMutexLocker</span><span class="p">(</span><span class="n">QMutex</span> <span class="o">*</span><span class="n">mutex</span><span class="p">)</span> <span class="o">:</span> <span class="n">mutex</span><span class="p">(</span><span class="n">mutex</span><span class="p">)</span> <span class="p">{</span> <span class="n">mutex</span><span class="o">-&gt;</span><span class="n">lock</span><span class="p">();</span> <span class="p">}</span> <span class="o">~</span><span class="n">QMutexLocker</span><span class="p">()</span> <span class="p">{</span> <span class="n">mutex</span><span class="o">-&gt;</span><span class="n">unlock</span><span class="p">();</span> <span class="p">}</span> <span class="p">};</span></code></pre></figure> <p>Comme l’objet est détruit lors de la sortie du <em>scope</em> de la méthode (que ce soit par un <code class="highlighter-rouge">return</code> ou par une exception survenue n’importe où), le <em>mutex</em> sera <strong>toujours</strong> déverrouillé.</p> <p>Au passage, dans l’exemple ci-dessus, nous remarquons que la variable <code class="highlighter-rouge">locker</code> n’est jamais utilisée. RAII complexifie donc la détection des <em>variables inutilisées</em>, car le compilateur doit détecter les effets de bords. Mais il s’en sort bien : ici, il n’émet pas de <em>warning</em>.</p> <h2 id="smart-pointers">Smart pointers</h2> <p>Les <a href="https://en.wikipedia.org/wiki/Smart_pointer">smart pointers</a> utilisent RAII pour gérer automatiquement la durée de vie des pointeurs. Il en existe plusieurs.</p> <p>Dans la STL :</p> <ul> <li><a href="http://en.cppreference.com/w/cpp/memory/unique_ptr"><code class="highlighter-rouge">std::unique_ptr</code></a></li> <li><a href="http://en.cppreference.com/w/cpp/memory/shared_ptr"><code class="highlighter-rouge">std::shared_ptr</code></a></li> <li><a href="http://en.cppreference.com/w/cpp/memory/weak_ptr"><code class="highlighter-rouge">std::weak_ptr</code></a></li> <li><a href="http://en.cppreference.com/w/cpp/memory/auto_ptr"><code class="highlighter-rouge">std::auto_ptr</code></a> <em>(à bannir)</em></li> </ul> <p>Dans Qt :</p> <ul> <li><a href="http://doc.qt.io/qt-5/qsharedpointer.html"><code class="highlighter-rouge">QSharedPointer</code></a> (équivalent de <code class="highlighter-rouge">std::shared_ptr</code>)</li> <li><a href="http://doc.qt.io/qt-5/qweakpointer.html"><code class="highlighter-rouge">QWeakPointer</code></a> (équivalent de <code class="highlighter-rouge">std::weak_ptr</code>)</li> <li><a href="http://doc.qt.io/qt-5/qscopedpointer.html"><code class="highlighter-rouge">QScopedPointer</code></a> (ersatz de <code class="highlighter-rouge">std::unique_ptr</code>)</li> <li><a href="http://doc.qt.io/qt-5/qscopedarraypointer.html"><code class="highlighter-rouge">QScopedArrayPointer</code></a></li> <li><a href="http://doc.qt.io/qt-5/qpointer.html"><code class="highlighter-rouge">QPointer</code></a></li> <li><a href="http://doc.qt.io/qt-5/qshareddatapointer.html"><code class="highlighter-rouge">QSharedDataPointer</code></a></li> <li><a href="http://doc.qt.io/qt-5/qexplicitlyshareddatapointer.html"><code class="highlighter-rouge">QExplicitlySharedDataPointer</code></a></li> </ul> <h3 id="scoped-pointers">Scoped pointers</h3> <p>Le <em>smart pointer</em> le plus simple est le <em>scoped pointer</em>. L’idée est vraiment la même que <a href="http://doc.qt.io/qt-5/qmutexlocker.html"><code class="highlighter-rouge">QMutexLocker</code></a>, sauf qu’au lieu de vérouiller et déverrouiller un <em>mutex</em>, il stocke un <em>raw pointer</em> et le supprime.</p> <p>En plus de cela, comme tous les <em>smart pointers</em>, il <a href="https://en.wikipedia.org/wiki/Operator_overloading">redéfinit certains opérateurs</a> pour pouvoir être utilisé comme un <em>raw pointer</em>.</p> <p>Par exemple, voici une implémentation simplifiée possible de <a href="http://doc.qt.io/qt-5/qscopedpointer.html"><code class="highlighter-rouge">QScopedPointer</code></a> :</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="k">template</span> <span class="o">&lt;</span><span class="k">typename</span> <span class="n">T</span><span class="o">&gt;</span> <span class="k">class</span> <span class="nc">QScopedPointer</span> <span class="p">{</span> <span class="n">T</span> <span class="o">*</span><span class="n">p</span><span class="p">;</span> <span class="k">public</span><span class="o">:</span> <span class="k">explicit</span> <span class="n">QScopedPointer</span><span class="p">(</span><span class="n">T</span> <span class="o">*</span><span class="n">p</span><span class="p">)</span> <span class="o">:</span> <span class="n">p</span><span class="p">(</span><span class="n">p</span><span class="p">)</span> <span class="p">{}</span> <span class="o">~</span><span class="n">QScopedPointer</span><span class="p">()</span> <span class="p">{</span> <span class="k">delete</span> <span class="n">p</span><span class="p">;</span> <span class="p">}</span> <span class="n">T</span> <span class="o">*</span><span class="n">data</span><span class="p">()</span> <span class="k">const</span> <span class="p">{</span> <span class="k">return</span> <span class="n">p</span><span class="p">;</span> <span class="p">}</span> <span class="k">operator</span> <span class="kt">bool</span><span class="p">()</span> <span class="k">const</span> <span class="p">{</span> <span class="k">return</span> <span class="n">p</span><span class="p">;</span> <span class="p">}</span> <span class="n">T</span> <span class="o">&amp;</span><span class="k">operator</span><span class="o">*</span><span class="p">()</span> <span class="k">const</span> <span class="p">{</span> <span class="k">return</span> <span class="o">*</span><span class="n">p</span><span class="p">;</span> <span class="p">}</span> <span class="n">T</span> <span class="o">*</span><span class="k">operator</span><span class="o">-&gt;</span><span class="p">()</span> <span class="k">const</span> <span class="p">{</span> <span class="k">return</span> <span class="n">p</span><span class="p">;</span> <span class="p">}</span> <span class="k">private</span><span class="o">:</span> <span class="n">Q_DISABLE_COPY</span><span class="p">(</span><span class="n">QScopedPointer</span><span class="p">)</span> <span class="p">};</span></code></pre></figure> <p>Et un exemple d’utilisation :</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="c1">// bad design (owning raw pointer) </span><span class="n">DeviceInfo</span> <span class="o">*</span><span class="n">Device</span><span class="o">::</span><span class="n">getDeviceInfo</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="k">new</span> <span class="n">DeviceInfo</span><span class="p">(</span><span class="cm">/* … */</span><span class="p">);</span> <span class="p">}</span> <span class="kt">void</span> <span class="n">Device</span><span class="o">::</span><span class="n">printInfo</span><span class="p">()</span> <span class="p">{</span> <span class="n">QScopedPointer</span><span class="o">&lt;</span><span class="n">DeviceInfo</span><span class="o">&gt;</span> <span class="n">info</span><span class="p">(</span><span class="n">getDeviceInfo</span><span class="p">());</span> <span class="c1">// used like a raw pointer </span> <span class="k">if</span> <span class="p">(</span><span class="n">info</span><span class="p">)</span> <span class="p">{</span> <span class="n">qDebug</span><span class="p">()</span> <span class="o">&lt;&lt;</span> <span class="n">info</span><span class="o">-&gt;</span><span class="n">getId</span><span class="p">();</span> <span class="n">DeviceInfo</span> <span class="n">copy</span> <span class="o">=</span> <span class="o">*</span><span class="n">info</span><span class="p">;</span> <span class="p">}</span> <span class="c1">// automatically deleted </span><span class="p">}</span></code></pre></figure> <h3 id="shared-pointers">Shared pointers</h3> <p>Les <em>shared pointers</em> permettent de partager l’<em>ownership</em> (la responsabilité de suppression) d’une ressource.</p> <p>Ils contiennent un <a href="https://en.wikipedia.org/wiki/Reference_counting">compteur de références</a>, indiquant le nombre d’instances partageant le même pointeur. Lorsque ce compteur tombe à 0, le pointeur est supprimé (il faut donc éviter les <a href="https://en.wikipedia.org/wiki/Reference_counting#Dealing_with_reference_cycles">cycles</a>).</p> <p>En pratique, voici ce à quoi ressemblerait une liste de <code class="highlighter-rouge">Device</code>s <em>partagés</em> par des <a href="http://doc.qt.io/qt-5/qsharedpointer.html"><code class="highlighter-rouge">QSharedPointer</code></a>s :</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="k">class</span> <span class="nc">DeviceList</span> <span class="p">{</span> <span class="n">QList</span><span class="o">&lt;</span><span class="n">QSharedPointer</span><span class="o">&lt;</span><span class="n">Device</span><span class="o">&gt;&gt;</span> <span class="n">devices</span><span class="p">;</span> <span class="k">public</span><span class="o">:</span> <span class="n">QSharedPointer</span><span class="o">&lt;</span><span class="n">Device</span><span class="o">&gt;</span> <span class="n">getDevice</span><span class="p">(</span><span class="kt">int</span> <span class="n">index</span><span class="p">)</span> <span class="k">const</span><span class="p">;</span> <span class="kt">void</span> <span class="n">add</span><span class="p">(</span><span class="k">const</span> <span class="n">QSharedPointer</span><span class="o">&lt;</span><span class="n">Device</span><span class="o">&gt;</span> <span class="o">&amp;</span><span class="n">device</span><span class="p">);</span> <span class="kt">void</span> <span class="n">remove</span><span class="p">(</span><span class="n">Device</span> <span class="o">*</span><span class="n">device</span><span class="p">);</span> <span class="p">};</span> <span class="o">//</span> <span class="n">devices</span> <span class="n">are</span> <span class="n">automatically</span> <span class="n">deleted</span> <span class="n">when</span> <span class="n">necessary</span></code></pre></figure> <p>Le <em>partage</em> d’un pointeur découle toujours de la copie d’un <em>shared pointer</em>. C’est la raison pour laquelle <code class="highlighter-rouge">getDevice(…)</code> et <code class="highlighter-rouge">add(…)</code> manipulent un <a href="http://doc.qt.io/qt-5/qsharedpointer.html"><code class="highlighter-rouge">QSharedPointer</code></a>.</p> <p>Le piège à éviter est de créér plusieurs <em>smart pointers</em> indépendants sur le même <em>raw pointer</em>. Dans ce cas, il y aurait deux <em>refcounts</em> à 1 plutôt qu’un <em>refcount</em> à 2, et le pointeur serait supprimé dès la destruction du premier <em>shared pointer</em>, laissant l’autre <a href="https://fr.wikipedia.org/wiki/Dangling_pointer"><em>pendouillant</em></a>.</p> <p><em>Petite parenthèse : la signature des méthodes <code class="highlighter-rouge">add</code> et <code class="highlighter-rouge">remove</code> sont différentes car une suppression ne nécessite pas de <a href="https://www.youtube.com/watch?v=xnqTKD8uD64&amp;t=18m38s">manipuler la durée de vie</a> du <code class="highlighter-rouge">Device</code> passé en paramètre.</em></p> <blockquote> <p>Refcounted smart pointers are about managing te owned object’s lifetime.</p> <p>Copy/assign one only when you intend to manipulate the owned object’s lifetime.</p> </blockquote> <p>Au passage, si en Qt vous passez vos objets de la couche C++ à la couche <a href="https://en.wikipedia.org/wiki/QML">QML</a>, il faut aussi passer les <em>shared pointers</em> afin de ne pas casser le partage, ce qui implique d’enregistrer le type :</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="kt">void</span> <span class="nf">registerQml</span><span class="p">()</span> <span class="p">{</span> <span class="n">qRegisterMetaType</span><span class="o">&lt;</span><span class="n">QSharedPointer</span><span class="o">&lt;</span><span class="n">Device</span><span class="o">&gt;&gt;</span><span class="p">();</span> <span class="p">}</span></code></pre></figure> <p>Listons donc les avantages des <em>shared pointers</em> :</p> <ul> <li>la gestion mémoire est automatique ;</li> <li>l’<em>ownership</em> est géré automatiquement ;</li> <li>l’utilisation est moins propice aux erreurs (à part la possibilité de créer des <em>smart pointers</em> indépendants sur le même <em>raw pointer</em>) ;</li> </ul> <p>Cependant, si la gestion mémoire est <strong>automatique</strong>, elle n’est pas <strong>transparente</strong> : elle nécessite de manipuler explicitement des <code class="highlighter-rouge">QSharedPointer</code>, ce qui est verbeux.</p> <p>Il est certes possible d’utiliser un <a href="http://en.cppreference.com/w/cpp/language/type_alias">alias</a> (<a href="https://en.wikipedia.org/wiki/Typedef">typedef</a>) pour atténuer la verbosité :</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="k">using</span> <span class="n">DevicePtr</span> <span class="o">=</span> <span class="n">QSharedPointer</span><span class="o">&lt;</span><span class="n">Device</span><span class="o">&gt;</span><span class="p">;</span> <span class="k">class</span> <span class="nc">DeviceList</span> <span class="p">{</span> <span class="n">QList</span><span class="o">&lt;</span><span class="n">DevicePtr</span><span class="o">&gt;</span> <span class="n">devices</span><span class="p">;</span> <span class="k">public</span><span class="o">:</span> <span class="n">DevicePtr</span> <span class="n">getDevice</span><span class="p">(</span><span class="kt">int</span> <span class="n">index</span><span class="p">)</span> <span class="k">const</span><span class="p">;</span> <span class="kt">void</span> <span class="n">add</span><span class="p">(</span><span class="k">const</span> <span class="n">DevicePtr</span> <span class="o">&amp;</span><span class="n">device</span><span class="p">);</span> <span class="kt">void</span> <span class="n">remove</span><span class="p">(</span><span class="n">Device</span> <span class="o">*</span><span class="n">device</span><span class="p">);</span> <span class="p">};</span></code></pre></figure> <p>Mais quoi qu’il en soit, <strong>cela reste plus complexe que des <em>valeurs</em></strong>.</p> <p>Pour aller plus loin, nous allons devoir faire un détour inattendu, par un <em>idiome</em> qui n’a a priori rien à voir.</p> <h2 id="pimpl">PImpl</h2> <p><a href="https://en.wikipedia.org/wiki/Opaque_pointer">PImpl</a> sert à réduire les dépendances de compilation.</p> <blockquote> <p>Opaque pointers are a way to hide the implementation details of an interface from ordinary clients, so that the implementation may be changed without the need to recompile the modules using it.</p> </blockquote> <p>Prenons la classe <code class="highlighter-rouge">Person</code> suivante (<code class="highlighter-rouge">person.h</code>) :</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="k">class</span> <span class="nc">Person</span> <span class="p">{</span> <span class="n">QString</span> <span class="n">name</span><span class="p">;</span> <span class="kt">long</span> <span class="n">birth</span><span class="p">;</span> <span class="k">public</span><span class="o">:</span> <span class="n">Person</span><span class="p">(</span><span class="k">const</span> <span class="n">QString</span> <span class="o">&amp;</span><span class="n">name</span><span class="p">,</span> <span class="kt">long</span> <span class="n">birth</span><span class="p">);</span> <span class="n">QString</span> <span class="n">getName</span><span class="p">()</span> <span class="k">const</span><span class="p">;</span> <span class="kt">void</span> <span class="n">setName</span><span class="p">(</span><span class="k">const</span> <span class="n">QString</span> <span class="o">&amp;</span><span class="n">name</span><span class="p">);</span> <span class="kt">int</span> <span class="n">getAge</span><span class="p">()</span> <span class="k">const</span><span class="p">;</span> <span class="k">private</span><span class="o">:</span> <span class="k">static</span> <span class="kt">long</span> <span class="n">countYears</span><span class="p">(</span><span class="kt">long</span> <span class="n">from</span><span class="p">,</span> <span class="kt">long</span> <span class="n">to</span><span class="p">);</span> <span class="p">};</span></code></pre></figure> <p>Elle contient juste un <em>nom</em> et un <em>âge</em>. Elle définit par ailleurs une méthode privée, <code class="highlighter-rouge">countYears(…)</code>, qu’on imagine appelée dans <code class="highlighter-rouge">getAge()</code>.</p> <p>Chaque classe désirant utiliser la classe <code class="highlighter-rouge">Person</code> devra l’inclure :</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="cp">#include "person.h"</span></code></pre></figure> <p>Par conséquent, à chaque modification de ces parties privées (qui sont pourtant que des détails d’implémentation), <strong>toutes les classes incluant <code class="highlighter-rouge">person.h</code> devront être recompilées</strong>.</p> <p>C’est ce que <em>PImpl</em> permet d’éviter, en séparant la classe en deux :</p> <ul> <li>une interface publique ;</li> <li>une implémentation privée.</li> </ul> <p>Concrètement, la classe <code class="highlighter-rouge">Person</code> précédente est la partie privée. Renommons-la :</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="k">class</span> <span class="nc">PersonPrivate</span> <span class="p">{</span> <span class="n">QString</span> <span class="n">name</span><span class="p">;</span> <span class="kt">long</span> <span class="n">birth</span><span class="p">;</span> <span class="k">public</span><span class="o">:</span> <span class="n">PersonPrivate</span><span class="p">(</span><span class="k">const</span> <span class="n">QString</span> <span class="o">&amp;</span><span class="n">name</span><span class="p">,</span> <span class="kt">long</span> <span class="n">birth</span><span class="p">);</span> <span class="n">QString</span> <span class="n">getName</span><span class="p">()</span> <span class="k">const</span><span class="p">;</span> <span class="kt">void</span> <span class="n">setName</span><span class="p">(</span><span class="k">const</span> <span class="n">QString</span> <span class="o">&amp;</span><span class="n">name</span><span class="p">);</span> <span class="kt">int</span> <span class="n">getAge</span><span class="p">()</span> <span class="k">const</span><span class="p">;</span> <span class="k">private</span><span class="o">:</span> <span class="k">static</span> <span class="kt">long</span> <span class="n">countYears</span><span class="p">(</span><span class="kt">long</span> <span class="n">from</span><span class="p">,</span> <span class="kt">long</span> <span class="n">to</span><span class="p">);</span> <span class="p">};</span></code></pre></figure> <p>Créons la partie publique, définissant l’interface souhaitée :</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="k">class</span> <span class="nc">PersonPrivate</span><span class="p">;</span> <span class="c1">// forward declaration </span><span class="k">class</span> <span class="nc">Person</span> <span class="p">{</span> <span class="n">PersonPrivate</span> <span class="o">*</span><span class="n">d</span><span class="p">;</span> <span class="k">public</span><span class="o">:</span> <span class="n">Person</span><span class="p">(</span><span class="k">const</span> <span class="n">QString</span> <span class="o">&amp;</span><span class="n">name</span><span class="p">,</span> <span class="kt">long</span> <span class="n">birth</span><span class="p">);</span> <span class="n">Person</span><span class="p">(</span><span class="k">const</span> <span class="n">Person</span> <span class="o">&amp;</span><span class="n">other</span><span class="p">);</span> <span class="o">~</span><span class="n">Person</span><span class="p">();</span> <span class="n">Person</span> <span class="o">&amp;</span><span class="k">operator</span><span class="o">=</span><span class="p">(</span><span class="k">const</span> <span class="n">Person</span> <span class="o">&amp;</span><span class="n">other</span><span class="p">);</span> <span class="n">QString</span> <span class="n">getName</span><span class="p">()</span> <span class="k">const</span><span class="p">;</span> <span class="kt">void</span> <span class="n">setName</span><span class="p">(</span><span class="k">const</span> <span class="n">QString</span> <span class="o">&amp;</span><span class="n">name</span><span class="p">);</span> <span class="kt">int</span> <span class="n">getAge</span><span class="p">()</span> <span class="k">const</span><span class="p">;</span> <span class="p">};</span></code></pre></figure> <p>Elle contient un pointeur vers <code class="highlighter-rouge">PersonPrivate</code>, et lui délègue tous les appels.</p> <p>Évidemment, <code class="highlighter-rouge">Person</code> ne doit pas inclure <code class="highlighter-rouge">PersonPrivate</code>, sinon nous aurions les mêmes dépendances de compilation, et nous n’aurions rien résolu. Il faut utiliser à la place une <a href="https://en.wikipedia.org/wiki/Forward_declaration"><em>forward declaration</em></a>.</p> <p>Voici son implémentation :</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="n">Person</span><span class="o">::</span><span class="n">Person</span><span class="p">(</span><span class="k">const</span> <span class="n">QString</span> <span class="o">&amp;</span><span class="n">name</span><span class="p">,</span> <span class="kt">long</span> <span class="n">birth</span><span class="p">)</span> <span class="o">:</span> <span class="n">d</span><span class="p">(</span><span class="k">new</span> <span class="n">PersonPrivate</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="n">birth</span><span class="p">))</span> <span class="p">{}</span> <span class="n">Person</span><span class="o">::</span><span class="n">Person</span><span class="p">(</span><span class="k">const</span> <span class="n">Person</span> <span class="o">&amp;</span><span class="n">other</span><span class="p">)</span> <span class="o">:</span> <span class="n">d</span><span class="p">(</span><span class="k">new</span> <span class="n">PersonPrivate</span><span class="p">(</span><span class="o">*</span><span class="n">other</span><span class="p">.</span><span class="n">d</span><span class="p">))</span> <span class="p">{}</span> <span class="n">Person</span><span class="o">::~</span><span class="n">Person</span><span class="p">()</span> <span class="p">{</span> <span class="k">delete</span> <span class="n">d</span><span class="p">;</span> <span class="p">}</span> <span class="n">Person</span> <span class="o">&amp;</span><span class="n">Person</span><span class="o">::</span><span class="k">operator</span><span class="o">=</span><span class="p">(</span><span class="k">const</span> <span class="n">Person</span> <span class="o">&amp;</span><span class="n">other</span><span class="p">)</span> <span class="p">{</span> <span class="o">*</span><span class="n">d</span> <span class="o">=</span> <span class="o">*</span><span class="n">other</span><span class="p">.</span><span class="n">d</span><span class="p">;</span> <span class="k">return</span> <span class="o">*</span><span class="k">this</span><span class="p">;</span> <span class="p">}</span> <span class="n">QString</span> <span class="n">Person</span><span class="o">::</span><span class="n">getName</span><span class="p">()</span> <span class="k">const</span> <span class="p">{</span> <span class="k">return</span> <span class="n">d</span><span class="o">-&gt;</span><span class="n">getName</span><span class="p">();</span> <span class="p">}</span> <span class="kt">void</span> <span class="n">Person</span><span class="o">::</span><span class="n">setName</span><span class="p">(</span><span class="k">const</span> <span class="n">QString</span> <span class="o">&amp;</span><span class="n">name</span><span class="p">)</span> <span class="p">{</span> <span class="n">d</span><span class="o">-&gt;</span><span class="n">setName</span><span class="p">(</span><span class="n">name</span><span class="p">);</span> <span class="p">}</span> <span class="kt">int</span> <span class="n">Person</span><span class="o">::</span><span class="n">getAge</span><span class="p">()</span> <span class="k">const</span> <span class="p">{</span> <span class="k">return</span> <span class="n">d</span><span class="o">-&gt;</span><span class="n">getAge</span><span class="p">();</span> <span class="p">}</span></code></pre></figure> <p>Le pointeur vers la classe privée est souvent nommé <code class="highlighter-rouge">d</code> car il s’agit d’un <a href="https://wiki.qt.io/D-Pointer">d-pointer</a>.</p> <p>Donc comme prévu, tout cela n’a rien à voir avec notre objectif d’éviter d’utiliser des pointeurs.</p> <h2 id="partage">Partage</h2> <p>Mais en fait, si. <em>PImpl</em> permet de séparer les classes manipulées explicitement de l’objet réellement modifié :</p> <p class="center"><img src="/assets/nopointers/pimpl.png" alt="graph_pimpl" /></p> <p>Il y a une relation 1-1 entre la classe publique et la classe privée correspondante. Mais nous pouvons imaginer d’autres <a href="https://fr.wikipedia.org/wiki/Cardinalit%C3%A9_%28programmation%29">cardinalités</a>.</p> <p>Par exemple, Qt <a href="http://doc.qt.io/qt-5/implicit-sharing.html">partage implicitement</a> les parties privées d’un grand nombre de <a href="http://doc.qt.io/qt-5/implicit-sharing.html#list-of-classes">classes</a>. Il ne les copie que lors d’une écriture (<a href="https://fr.wikipedia.org/wiki/Copy-on-write">CoW</a>) :</p> <p class="center"><img src="/assets/nopointers/pimpl_shareddata.png" alt="graph_pimpl_shareddata" /></p> <p>Par exemple, lorsqu’une <a href="http://doc.qt.io/qt-5/qstring.html"><code class="highlighter-rouge">QString</code></a> est copiée, la même zone mémoire sera utilisée pour les différentes instances, jusqu’à ce qu’une modification survienne.</p> <p>Cependant, il ne s’agit que d’un détail d’implémentation utilisé pour améliorer les performances. Du point de vue utilisateur, tout se passe comme si les données étaient réellement copiées :</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="n">QString</span> <span class="n">s1</span> <span class="o">=</span> <span class="s">"ABC"</span><span class="p">;</span> <span class="n">QString</span> <span class="n">s2</span> <span class="o">=</span> <span class="n">s1</span><span class="p">;</span> <span class="n">s2</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="s">"DEF"</span><span class="p">);</span> <span class="n">Q_ASSERT</span><span class="p">(</span><span class="n">s2</span> <span class="o">==</span> <span class="s">"ABCDEF"</span><span class="p">);</span> <span class="n">Q_ASSERT</span><span class="p">(</span><span class="n">s1</span> <span class="o">==</span> <span class="s">"ABC"</span><span class="p">);</span></code></pre></figure> <p>En d’autres termes, <strong>les classes publiques ci-dessus ont une <a href="https://en.wikipedia.org/wiki/Value_semantics">sémantique de valeur</a></strong>.</p> <h3 id="resource-handles">Resource handles</h3> <p>À la place, nous pouvons décider de partager inconditionnellement la partie privée, y compris après une écriture :</p> <p class="center"><img src="/assets/nopointers/pimpl_shared.png" alt="graph_pimpl_shared" /></p> <p>Dans ce cas, <strong>la classe publique a sémantique d’entité</strong>. Elle est qualifiée de <em>resource handle</em>.</p> <p>C’est bien sûr le cas des <em>smart pointers</em> :</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="n">QSharedPointer</span><span class="o">&lt;</span><span class="n">Person</span><span class="o">&gt;</span> <span class="n">p1</span><span class="p">(</span><span class="k">new</span> <span class="n">Person</span><span class="p">(</span><span class="s">"ABC"</span><span class="p">,</span> <span class="mi">42</span><span class="p">));</span> <span class="n">QSharedPointer</span><span class="o">&lt;</span><span class="n">person</span><span class="o">&gt;</span> <span class="n">p2</span> <span class="o">=</span> <span class="n">p1</span><span class="p">;</span> <span class="n">p2</span><span class="o">-&gt;</span><span class="n">setName</span><span class="p">(</span><span class="s">"DEF"</span><span class="p">);</span> <span class="n">Q_ASSERT</span><span class="p">(</span><span class="n">p1</span><span class="p">.</span><span class="n">getName</span><span class="p">()</span> <span class="o">==</span> <span class="s">"DEF"</span><span class="p">);</span> <span class="n">Q_ASSERT</span><span class="p">(</span><span class="n">p2</span><span class="p">.</span><span class="n">getName</span><span class="p">()</span> <span class="o">==</span> <span class="s">"DEF"</span><span class="p">);</span></code></pre></figure> <p>Mais aussi d’autres classes, comme l’<a href="http://doc.qt.io/qt-5/qdomdocument.html#details">API Dom de Qt</a> :</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="kt">void</span> <span class="nf">addItem</span><span class="p">(</span><span class="k">const</span> <span class="n">QDomDocument</span> <span class="o">&amp;</span><span class="n">document</span><span class="p">,</span> <span class="k">const</span> <span class="n">QDomElement</span> <span class="o">&amp;</span><span class="n">element</span><span class="p">)</span> <span class="p">{</span> <span class="n">QDomElement</span> <span class="n">root</span> <span class="o">=</span> <span class="n">document</span><span class="p">.</span><span class="n">documentElement</span><span class="p">();</span> <span class="n">root</span><span class="p">.</span><span class="n">insertAfter</span><span class="p">(</span><span class="n">element</span><span class="p">,</span> <span class="p">{});</span> <span class="c1">// the document is modified </span><span class="p">}</span></code></pre></figure> <h3 id="pimpl-avec-des-smart-pointers">PImpl avec des <em>smart pointers</em></h3> <p>Tout-à-l’heure, j’ai présenté <em>PImpl</em> en utilisant un <em>owning raw pointer</em> :</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="k">class</span> <span class="nc">PersonPrivate</span><span class="p">;</span> <span class="c1">// forward declaration </span><span class="k">class</span> <span class="nc">Person</span> <span class="p">{</span> <span class="c1">// this is a raw pointer! </span> <span class="n">PersonPrivate</span> <span class="o">*</span><span class="n">d</span><span class="p">;</span> <span class="k">public</span><span class="o">:</span> <span class="c1">// … </span><span class="p">};</span></code></pre></figure> <p>Mais en fait, à chaque type de relation correspond un type de <em>smart pointer</em> directement utilisable pour <em>PImpl</em>.</p> <p>Pour une relation 1-1 classique :</p> <ul> <li><a href="http://en.cppreference.com/w/cpp/memory/unique_ptr"><code class="highlighter-rouge">std::unique_ptr</code></a></li> <li><a href="http://doc.qt.io/qt-5/qscopedpointer.html"><code class="highlighter-rouge">QScopedPointer</code></a></li> </ul> <p>Pour une relation 1-N à sémantique de valeur (CoW) :</p> <ul> <li><a href="http://doc.qt.io/qt-5/qshareddatapointer.html"><code class="highlighter-rouge">QSharedDataPointer</code></a></li> </ul> <p>Pour une relation 1-N à sémantique d’entité :</p> <ul> <li><a href="http://en.cppreference.com/w/cpp/memory/shared_ptr"><code class="highlighter-rouge">std::shared_ptr</code></a></li> <li><a href="http://doc.qt.io/qt-5/qsharedpointer.html"><code class="highlighter-rouge">QSharedPointer</code></a></li> </ul> <p>Par exemple, donnons à notre classe <code class="highlighter-rouge">Person</code> une sémantique d’entité :</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="k">class</span> <span class="nc">PersonPrivate</span><span class="p">;</span> <span class="c1">// forward declaration </span><span class="k">class</span> <span class="nc">Person</span> <span class="p">{</span> <span class="n">QSharedPointer</span><span class="o">&lt;</span><span class="n">PersonPrivate</span><span class="o">&gt;</span> <span class="n">d</span><span class="p">;</span> <span class="k">public</span><span class="o">:</span> <span class="n">Person</span><span class="p">()</span> <span class="o">=</span> <span class="k">default</span><span class="p">;</span> <span class="c1">// a "null" person </span> <span class="n">Person</span><span class="p">(</span><span class="k">const</span> <span class="n">QString</span> <span class="o">&amp;</span><span class="n">name</span><span class="p">,</span> <span class="kt">long</span> <span class="n">birth</span><span class="p">);</span> <span class="n">QString</span> <span class="n">getName</span><span class="p">()</span> <span class="k">const</span><span class="p">;</span> <span class="c1">// shared handles should expose const methods </span> <span class="kt">void</span> <span class="n">setName</span><span class="p">(</span><span class="k">const</span> <span class="n">QString</span> <span class="o">&amp;</span><span class="n">name</span><span class="p">)</span> <span class="k">const</span><span class="p">;</span> <span class="kt">int</span> <span class="n">getAge</span><span class="p">()</span> <span class="k">const</span><span class="p">;</span> <span class="k">operator</span> <span class="kt">bool</span><span class="p">()</span> <span class="k">const</span> <span class="p">{</span> <span class="k">return</span> <span class="n">d</span><span class="p">;</span> <span class="p">}</span> <span class="p">};</span></code></pre></figure> <p><code class="highlighter-rouge">Person</code> se comporte maintenant <em>comme un pointeur</em>.</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="n">Person</span> <span class="n">p1</span><span class="p">(</span><span class="s">"ABC"</span><span class="p">,</span> <span class="mi">42</span><span class="p">);</span> <span class="n">Person</span> <span class="n">p2</span> <span class="o">=</span> <span class="n">p1</span><span class="p">;</span> <span class="n">p2</span><span class="p">.</span><span class="n">setName</span><span class="p">(</span><span class="s">"DEF"</span><span class="p">);</span> <span class="n">Q_ASSERT</span><span class="p">(</span><span class="n">p1</span><span class="p">.</span><span class="n">getName</span><span class="p">()</span> <span class="o">==</span> <span class="s">"DEF"</span><span class="p">);</span> <span class="n">Q_ASSERT</span><span class="p">(</span><span class="n">p2</span><span class="p">.</span><span class="n">getName</span><span class="p">()</span> <span class="o">==</span> <span class="s">"DEF"</span><span class="p">);</span></code></pre></figure> <p><code class="highlighter-rouge">p1</code> et <code class="highlighter-rouge">p2</code> sont alors des <em>resource handles</em> vers <code class="highlighter-rouge">PersonPrivate</code> :</p> <p class="center"><img src="/assets/nopointers/shared_person.png" alt="graph_shared_person" /></p> <p>Évidemment, ce n’est pas approprié pour la classe <code class="highlighter-rouge">Person</code>, car le comportement est trop inattendu.</p> <p>Mais je vais présenter un cas réel où ce <em>design</em> est approprié.</p> <h2 id="en-pratique">En pratique</h2> <p><a id="libusb-wrappers"></a> Pour l’entreprise dans laquelle je suis salarié, j’ai implémenté une fonctionnalité permettant d’utiliser une souris USB branchée sur un PC pour contrôler un téléphone Android connecté en USB.</p> <p>Concrètement, cela consiste à tranférer (grâce à <a href="http://libusb.info"><code class="highlighter-rouge">libusb</code></a>), à partir du PC, les événements <a href="https://en.wikipedia.org/wiki/Human_interface_device">HID</a> reçus de la souris vers le téléphone Android.</p> <p>J’ai donc (entre autres) créé des <em>resources handles</em> <code class="highlighter-rouge">UsbDevice</code> et <code class="highlighter-rouge">UsbDeviceHandle</code> qui wrappent les structures C <a href="http://libusb.sourceforge.net/api-1.0/group__dev.html#ga77eedd00d01eb7569b880e861a971c2b"><code class="highlighter-rouge">libusb_device</code></a> et <a href="http://libusb.sourceforge.net/api-1.0/group__dev.html#ga7df95821d20d27b5597f1d783749d6a4"><code class="highlighter-rouge">libusb_device_handle</code></a>, suivant les principes détaillés dans ce billet.</p> <p>Leur utilisation illustre bien, d’après moi, les bénéfices d’une telle conception.</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="k">class</span> <span class="nc">UsbDeviceMonitor</span> <span class="p">{</span> <span class="n">QList</span><span class="o">&lt;</span><span class="n">UsbDevice</span><span class="o">&gt;</span> <span class="n">devices</span><span class="p">;</span> <span class="k">public</span><span class="o">:</span> <span class="c1">// … </span> <span class="n">UsbDevice</span> <span class="n">getAnyDroid</span><span class="p">()</span> <span class="k">const</span><span class="p">;</span> <span class="n">UsbDevice</span> <span class="n">getAnyMouse</span><span class="p">()</span> <span class="k">const</span><span class="p">;</span> <span class="n">signals</span><span class="o">:</span> <span class="kt">void</span> <span class="n">deviceArrived</span><span class="p">(</span><span class="k">const</span> <span class="n">UsbDevice</span> <span class="o">&amp;</span><span class="n">device</span><span class="p">);</span> <span class="kt">void</span> <span class="n">deviceLeft</span><span class="p">(</span><span class="k">const</span> <span class="n">UsbDevice</span> <span class="o">&amp;</span><span class="n">device</span><span class="p">);</span> <span class="p">};</span></code></pre></figure> <p><code class="highlighter-rouge">UsbDevice</code> peut être retourné par valeur, et passé en paramètre d’un <em>signal</em> par <em>const reference</em> (exactement comme nous le ferions avec un <a href="http://doc.qt.io/qt-5/qstring.html"><code class="highlighter-rouge">QString</code></a>).</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="n">UsbDevice</span> <span class="n">UsbDeviceMonitor</span><span class="o">::</span><span class="n">getAnyMouse</span><span class="p">()</span> <span class="k">const</span> <span class="p">{</span> <span class="k">for</span> <span class="p">(</span><span class="k">const</span> <span class="n">UsbDevice</span> <span class="o">&amp;</span><span class="n">device</span> <span class="o">:</span> <span class="n">devices</span><span class="p">)</span> <span class="k">if</span> <span class="p">(</span><span class="n">device</span><span class="p">.</span><span class="n">isMouse</span><span class="p">())</span> <span class="k">return</span> <span class="n">device</span><span class="p">;</span> <span class="k">return</span> <span class="p">{};</span> <span class="p">}</span></code></pre></figure> <p>Si une souris est trouvée dans la liste, on la retourne simplement ; sinon, on retourne un <code class="highlighter-rouge">UsbDevice</code> “<a href="http://doc.qt.io/qt-5/qstring.html#isNull"><em>null</em></a>”.</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="kt">void</span> <span class="n">UsbDeviceMonitor</span><span class="o">::</span><span class="n">onHotplugDeviceArrived</span><span class="p">(</span><span class="k">const</span> <span class="n">UsbDevice</span> <span class="o">&amp;</span><span class="n">device</span><span class="p">)</span> <span class="p">{</span> <span class="n">devices</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">device</span><span class="p">);</span> <span class="n">emit</span> <span class="n">deviceArrived</span><span class="p">(</span><span class="n">device</span><span class="p">);</span> <span class="p">}</span></code></pre></figure> <p>La gestion mémoire est totalement automatique et transparente. Les <a href="#suppression-complexe">problèmes présentés</a> sont résolus.</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="kt">void</span> <span class="nf">registerQml</span><span class="p">()</span> <span class="p">{</span> <span class="n">qRegisterMetaType</span><span class="o">&lt;</span><span class="n">UsbDevice</span><span class="o">&gt;</span><span class="p">();</span> <span class="p">}</span></code></pre></figure> <figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="c1">// QML</span> <span class="kd">function</span> <span class="nx">startForwarding</span><span class="p">()</span> <span class="p">{</span> <span class="kd">var</span> <span class="nx">mouse</span> <span class="o">=</span> <span class="nx">usbDeviceMonitor</span><span class="p">.</span><span class="nx">getAnyMouse</span><span class="p">()</span> <span class="kd">var</span> <span class="nx">droid</span> <span class="o">=</span> <span class="nx">usbDeviceMonitor</span><span class="p">.</span><span class="nx">getAnyDroid</span><span class="p">()</span> <span class="nx">worker</span> <span class="o">=</span> <span class="nx">hid</span><span class="p">.</span><span class="nx">forward</span><span class="p">(</span><span class="nx">mouse</span><span class="p">,</span> <span class="nx">droid</span><span class="p">)</span> <span class="p">}</span></code></pre></figure> <p><code class="highlighter-rouge">UsbDevice</code> peut naviguer entre la couche C++ et QML.</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="kt">bool</span> <span class="n">HID</span><span class="o">::</span><span class="n">forward</span><span class="p">(</span><span class="k">const</span> <span class="n">UsbDevice</span> <span class="o">&amp;</span><span class="n">mouse</span><span class="p">,</span> <span class="k">const</span> <span class="n">UsbDevice</span> <span class="o">&amp;</span><span class="n">droid</span><span class="p">)</span> <span class="p">{</span> <span class="n">UsbDeviceHandle</span> <span class="n">droidHandle</span> <span class="o">=</span> <span class="n">droid</span><span class="p">.</span><span class="n">open</span><span class="p">();</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">droidHandle</span><span class="p">)</span> <span class="k">return</span> <span class="nb">false</span><span class="p">;</span> <span class="n">UsbDeviceHandle</span> <span class="n">mouseHandle</span> <span class="o">=</span> <span class="n">mouse</span><span class="p">.</span><span class="n">open</span><span class="p">();</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">mouseHandle</span><span class="p">)</span> <span class="k">return</span> <span class="nb">false</span><span class="p">;</span> <span class="c1">// … </span><span class="p">}</span></code></pre></figure> <p>Grâce à RAII, les connexions (<code class="highlighter-rouge">UsbDeviceHandle</code>) sont fermées automatiquement.</p> <p>En particulier, si la connexion à la souris échoue, la connexion au téléphone Android est automatiquement fermée.</p> <h2 id="rsultat">Résultat</h2> <p>Dans ces différents exemples, <code class="highlighter-rouge">new</code> et <code class="highlighter-rouge">delete</code> ne sont jamais utilisés, et <strong>par construction, la mémoire sera correctement gérée</strong>. Ou plus précisément, si un problème de gestion mémoire existe, il se situera dans l’implémentation de la classe elle-même, et non partout où elle est utilisée.</p> <p>Ainsi, nous manipulons des <em>handles</em> se comportant comme des <em>pointeurs</em>, ayant les mêmes avantages que les <em>valeurs</em> :</p> <ul> <li>gestion mémoire <strong>automatique</strong> et <strong>transparente</strong> ;</li> <li>simple ;</li> <li>efficace ;</li> <li>sûr et robuste.</li> </ul> <p>Ils peuvent par contre présenter quelques limitations.</p> <p>Par exemple, ils sont incompatibles avec <a href="http://doc.qt.io/qt-5/qobject.html"><code class="highlighter-rouge">QObject</code></a>. En effet, techniquement, la classe d’un <em>resource handle</em> doit pouvoir être copiée (pour supporter le passage par <em>valeur</em>), alors qu’un <a href="http://doc.qt.io/qt-5/qobject.html"><code class="highlighter-rouge">QObject</code></a> <a href="http://doc.qt.io/qt-5/object.html#identity-vs-value">n’est pas copiable</a> :</p> <blockquote> <p><code class="highlighter-rouge">QObject</code>s are <em>identities</em>, not <em>values</em>.</p> </blockquote> <p>Très concrètement, cela implique que <code class="highlighter-rouge">UsbDevice</code> ne pourrait pas supporter de <em>signaux</em> (en tout cas, pas directement). C’est d’ailleurs le cas de beaucoup de classes de Qt : par exemple <a href="http://doc.qt.io/qt-5/qstring.html"><code class="highlighter-rouge">QString</code></a> et <a href="http://doc.qt.io/qt-5/qlist.html"><code class="highlighter-rouge">QList</code></a> n’héritent pas de <a href="http://doc.qt.io/qt-5/qobject.html"><code class="highlighter-rouge">QObject</code></a>.</p> <h2 id="rsum">Résumé</h2> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="k">auto</span> <span class="n">decide</span> <span class="o">=</span> <span class="p">[</span><span class="o">=</span><span class="p">]</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="n">semantics</span> <span class="o">==</span> <span class="n">VALUE</span><span class="p">)</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">mustAvoidCopies</span><span class="p">)</span> <span class="k">return</span> <span class="s">"just use values"</span><span class="p">;</span> <span class="k">return</span> <span class="s">"use PImpl + QSharedDataPointer"</span><span class="p">;</span> <span class="p">}</span> <span class="c1">// semantics == ENTITY </span> <span class="k">if</span> <span class="p">(</span><span class="n">entitySemanticsIsObvious</span><span class="p">)</span> <span class="k">return</span> <span class="s">"use PImpl + QSharedPointer"</span><span class="p">;</span> <span class="k">return</span> <span class="s">"use smart pointers explicitly"</span><span class="p">;</span> <span class="p">};</span></code></pre></figure> <p><em>C’est juste une heuristique…</em></p> <h2 id="conclusion">Conclusion</h2> <p>En suivant ces principes, nous pouvons nous débarrasser des <em>owning raw pointers</em> et des <code class="highlighter-rouge">new</code> et <code class="highlighter-rouge">delete</code> “nus”. Cela contribue à rendre le code plus simple et plus robuste.</p> <p>Ce sont d’ailleurs des objectifs qui guident les évolutions du langage C++ :</p> <ul> <li><a href="http://www.stroustrup.com/resource-model.pdf">A brief introduction to C++’s model for type and resource-safety</a></li> <li><a href="https://github.com/isocpp/CppCoreGuidelines/blob/master/talks/Stroustrup%20-%20CppCon%202015%20keynote.pdf">Writing good C++14</a></li> <li><a href="https://herbsutter.com/elements-of-modern-c-style/">Elements of Modern C++ Style</a></li> </ul> <p><code class="highlighter-rouge">return 0;</code></p> Commentaires statiques avec Jekyll 2017-01-09T18:06:04+01:00 http://blog.rom1v.com/2017/01/commentaires-statiques-avec-jekyll <p>Pour ce blog, j’ai abandonné <a href="https://fr.wikipedia.org/wiki/WordPres://fr.wordpress.org/">Wordpress</a> pour <a href="https://jekyllrb.com/">Jekyll</a>, un moteur de blog <em>statique</em>.</p> <p>Ainsi, j’écris mes articles en <a href="https://fr.wikipedia.org/wiki/Markdown">markdown</a> dans mon <a href="https://fr.wikipedia.org/wiki/Vim">éditeur favori</a>, je les <em>commite</em> dans un <a href="https://github.com/rom1v/blog.rom1v.com/">dépôt git</a>, et je génère le blog avec :</p> <div class="highlighter-rouge"><pre class="codehilite"><code>jekyll build </code></pre></div> <p>Le contenu hébergé étant statique, les pages ainsi générées à partir des sources sont renvoyées telles quelles.</p> <p>Ce fonctionnement a beaucoup d’avantages :</p> <ul> <li>le temps de réponse est minimal ;</li> <li>la sécurité est largement accrue ;</li> <li>la maintenance est simplifiée (pas de mises à jour de sécurité régulières) ;</li> <li>le backup est trivial (<code class="highlighter-rouge">git clone</code>, pas de base de données).</li> </ul> <h2 id="sans-commentaires">Sans commentaires</h2> <p>L’inconvénient, c’est qu’un contenu statique est difficilement conciliable avec le support des commentaires (il faut bien d’une manière ou d’une autre exécuter du code lors de la réception d’un commentaire).</p> <p>Il y a plusieurs manières de contourner le problème.</p> <p>Il est par exemple possible d’en déporter la gestion (sur un service en ligne comme <a href="http://www.perfectlyrandom.org/2014/06/29/adding-disqus-to-your-jekyll-powered-github-pages/">Disqus</a> ou un équivalent libre – <a href="https://posativ.org/isso/">isso</a> – à héberger soi-même). Ainsi, les commentaires peuvent être chargés séparément par le client en <em>Javascript</em>.</p> <p>Au lieu de cela, j’ai choisi d’intégrer les commentaires aux sources du blog. Voici comment.</p> <p>L’objectif est d’une part de pouvoir <strong>stocker</strong> et <strong>afficher</strong> les commentaires existants, et d’autre part de fournir aux lecteurs la possibilité d’en <strong>soumettre</strong> de nouveaux, qui me seront <strong>envoyés par e-mail</strong>.</p> <p>Je me suis principalement inspiré du contenu de <a href="http://theshed.hezmatt.org/jekyll-static-comments/">Jekyll::StaticComments</a>, même si, comme nous allons le voir, je n’utilise pas le plug-in lui-même.</p> <h2 id="stockage">Stockage</h2> <p>L’idée est de stocker les commentaires quelque part dans les sources du site au format <a href="https://fr.wikipedia.org/wiki/YAML">YAML</a>.</p> <p>Le plugin <em>Jekyll::StaticComments</em> nécessite de stocker <a href="https://github.com/mpalmer/jekyll-static-comments/blob/master/README.md#technical-details">un fichier par commentaire</a> dans un dossier spécial (<code class="highlighter-rouge">_comments</code>) parsé par un script à insérer dans le répertoire <code class="highlighter-rouge">_plugins</code>.</p> <p>Personnellement, je préfère avoir tous les commentaires d’un même post regroupés au sein d’un même fichier. Et pour cela, pas besoin de plug-in : nous pouvons faire <a href="http://stevesspace.com/2014/04/static-jekyll-comments/">correspondre</a> à chaque post dans <code class="highlighter-rouge">_posts</code> une liste de commentaires dans <code class="highlighter-rouge">_data</code> (un répertoire géré nativement par <em>Jekyll</em>).</p> <p>Par exemple, ce billet est stocké dans :</p> <div class="highlighter-rouge"><pre class="codehilite"><code>_posts/2017-01-09-commentaires-statiques-avec-jekyll.md </code></pre></div> <p>Dans l’idéal, je voudrais que les commentaires associés soient stockés dans :</p> <div class="highlighter-rouge"><pre class="codehilite"><code>_data/comments-2017-01-09-commentaires-statiques-avec-jekyll.yaml </code></pre></div> <p>En pratique, pour des raisons techniques (<a href="https://github.com/jekyll/jekyll/issues/633"><em>Jekyll</em> ne donne pas accès au nom du fichier</a>), le nom du fichier ne contient pas le numéro du jour :</p> <div class="highlighter-rouge"><pre class="codehilite"><code>_data/comments-2017-01-commentaires-statiques-avec-jekyll.yaml </code></pre></div> <p>Il suffit alors de stocker dans ces fichiers les commentaires sous cette forme :</p> <figure class="highlight"><pre><code class="language-yaml" data-lang="yaml"><span class="pi">-</span> <span class="s">id</span><span class="pi">:</span> <span class="s">1</span> <span class="s">author</span><span class="pi">:</span> <span class="s">this_is_me</span> <span class="s">date</span><span class="pi">:</span> <span class="s">2017-01-02 10:11:12+01:00</span> <span class="s">contents</span><span class="pi">:</span> <span class="pi">|</span> <span class="no">Bonjour,</span> <span class="no">Ceci est un commentaire écrit en _markdown_.</span> <span class="pi">-</span> <span class="s">id</span><span class="pi">:</span> <span class="s">2</span> <span class="s">author</span><span class="pi">:</span> <span class="s">dev42</span> <span class="s">author-url</span><span class="pi">:</span> <span class="s">https://github.com</span> <span class="s">date</span><span class="pi">:</span> <span class="s">2017-01-02 12:11:10+01:00</span> <span class="s">contents</span><span class="pi">:</span> <span class="pi">|</span> <span class="no">&gt; Ceci est un commentaire écrit en _markdown_.</span> <span class="no">Et ça supporte aussi le [Liquid](https://jekyllrb.com/docs/templates/) :</span> <span class="no">{% highlight c %}</span> <span class="no">int main() {</span> <span class="no">return 0;</span> <span class="no">}</span> <span class="no">{% endhighlight %}</span></code></pre></figure> <p>Pour des exemples réels, voir les <a href="https://github.com/rom1v/blog.rom1v.com/tree/master/_data">sources des commentaires</a> de ce blog.</p> <h2 id="affichage">Affichage</h2> <p>Maintenant que nous avons les données des commentaires, nous devons les afficher.</p> <p>Il faut d’abord trouver la liste des commentaires associée à la page courante.</p> <p>Comme nous ne pouvons pas récupérer directement le nom du fichier d’une page, nous devons reconstruire la chaîne à partir de la <a href="https://jekyllrb.com/docs/variables/#page-variables">variable</a> <code class="highlighter-rouge">page.id</code>, qui ici vaut :</p> <div class="highlighter-rouge"><pre class="codehilite"><code>/2017/01/commentaires-statiques-avec-jekyll </code></pre></div> <p>Cette ligne de <em>Liquid</em> :</p> <figure class="highlight"><pre><code class="language-liquid" data-lang="liquid">comments<span class="p">{{</span><span class="w"> </span><span class="nv">page</span><span class="p">.</span><span class="nv">id</span><span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="nf">replace</span><span class="p">:</span><span class="w"> </span><span class="s1">'/'</span><span class="p">,</span><span class="w"> </span><span class="s1">'-'</span><span class="w"> </span><span class="p">}}</span></code></pre></figure> <p>donne la valeur :</p> <div class="highlighter-rouge"><pre class="codehilite"><code>comments-2017-01-commentaires-statiques-avec-jekyll </code></pre></div> <p>Nous avons donc tout ce dont nous avons besoin pour créer le <em>template</em> de commentaires (à stocker dans <a href="https://github.com/rom1v/blog.rom1v.com/blob/master/_includes/comments.html"><code class="highlighter-rouge">_include/comments.html</code></a>) :</p> <figure class="highlight"><pre><code class="language-html" data-lang="html">{% capture commentid %}comments{{ page.id | replace: '/', '-' }}{% endcapture %} {% if site.data[commentid] %} <span class="nt">&lt;h2</span> <span class="na">id=</span><span class="s">"comments"</span><span class="nt">&gt;</span>Commentaires<span class="nt">&lt;/h2&gt;</span> <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"comments"</span><span class="nt">&gt;</span> {% for comment in site.data[commentid] %} <span class="nt">&lt;div</span> <span class="na">id=</span><span class="s">"comment-{{ comment.id }}"</span> <span class="na">class=</span><span class="s">"comment"</span> <span class="nt">/&gt;</span> <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"comment-author"</span><span class="nt">&gt;</span> {% if (comment.author-url) %} <span class="nt">&lt;a</span> <span class="na">href=</span><span class="s">"{{comment.author-url}}"</span><span class="nt">&gt;</span> {% endif %} {{ comment.author }} {% if (comment.author-url) %} <span class="nt">&lt;/a&gt;</span> {% endif %} <span class="nt">&lt;/div&gt;</span> <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"comment-date"</span><span class="nt">&gt;</span> <span class="nt">&lt;a</span> <span class="na">href=</span><span class="s">"#comment-{{ comment.id }}"</span><span class="nt">&gt;</span> {{ comment.date | date: "%-d %B %Y, %H:%M" }} <span class="nt">&lt;/a&gt;</span> <span class="nt">&lt;/div&gt;</span> <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"comment-contents"</span><span class="nt">&gt;</span> {{ comment.contents | liquify | markdownify }} <span class="nt">&lt;/div&gt;</span> <span class="nt">&lt;/div&gt;</span> {% endfor %} <span class="nt">&lt;/div&gt;</span></code></pre></figure> <p>Il suffit alors d’inclure cette page à l’endroit où vous souhaitez insérer les commentaires (typiquement dans <a href="https://github.com/rom1v/blog.rom1v.com/blob/master/_layouts/post.html"><code class="highlighter-rouge">_layout/post.html</code></a>) :</p> <figure class="highlight"><pre><code class="language-liquid" data-lang="liquid"><span class="p">{%</span><span class="w"> </span><span class="nt">include</span><span class="w"> </span><span class="na">comments</span><span class="p">.</span><span class="na">html</span><span class="w"> </span><span class="p">%}</span></code></pre></figure> <h2 id="formulaire">Formulaire</h2> <p>Pour proposer aux utilisateurs de poster de nouveaux commentaires, il nous faut un formulaire.</p> <p>À titre d’exemple, voici le mien (intégré à <a href="https://github.com/rom1v/blog.rom1v.com/blob/master/_includes/comments.html"><code class="highlighter-rouge">_include/comments.html</code></a>) :</p> <figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="nt">&lt;h3</span> <span class="na">class=</span><span class="s">"comment-title"</span><span class="nt">&gt;</span>Poster un commentaire<span class="nt">&lt;/h3&gt;</span> <span class="nt">&lt;form</span> <span class="na">method=</span><span class="s">"POST"</span> <span class="na">action=</span><span class="s">"/comments/submit.php"</span><span class="nt">&gt;</span> <span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">"hidden"</span> <span class="na">name=</span><span class="s">"post_id"</span> <span class="na">value=</span><span class="s">"{{ page.id }}"</span> <span class="nt">/&gt;</span> <span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">"hidden"</span> <span class="na">name=</span><span class="s">"return_url"</span> <span class="na">value=</span><span class="s">"{{ page.url }}"</span> <span class="nt">/&gt;</span> <span class="nt">&lt;table</span> <span class="na">class=</span><span class="s">"comment-table"</span><span class="nt">&gt;</span> <span class="nt">&lt;tr&gt;</span> <span class="nt">&lt;th&gt;</span>Nom :<span class="nt">&lt;/th&gt;</span> <span class="nt">&lt;td&gt;</span> <span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">"text"</span> <span class="na">size=</span><span class="s">"25"</span> <span class="na">name=</span><span class="s">"name"</span> <span class="nt">/&gt;</span> <span class="nt">&lt;em&gt;</span>(requis)<span class="nt">&lt;/em&gt;</span> <span class="nt">&lt;/td&gt;</span> <span class="nt">&lt;/tr&gt;</span> <span class="nt">&lt;tr&gt;</span> <span class="nt">&lt;th&gt;</span>E-mail :<span class="nt">&lt;/th&gt;</span> <span class="nt">&lt;td&gt;</span> <span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">"text"</span> <span class="na">size=</span><span class="s">"25"</span> <span class="na">name=</span><span class="s">"email"</span> <span class="nt">/&gt;</span> <span class="nt">&lt;em&gt;</span>(requis, non publié)<span class="nt">&lt;/em&gt;</span> <span class="nt">&lt;/td&gt;</span> <span class="nt">&lt;/tr&gt;</span> <span class="nt">&lt;tr&gt;</span> <span class="nt">&lt;th&gt;</span>Site web :<span class="nt">&lt;/th&gt;</span> <span class="nt">&lt;td&gt;</span> <span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">"text"</span> <span class="na">size=</span><span class="s">"25"</span> <span class="na">name=</span><span class="s">"url"</span> <span class="nt">/&gt;</span> <span class="nt">&lt;em&gt;</span>(optionnel)<span class="nt">&lt;/em&gt;</span> <span class="nt">&lt;/td&gt;</span> <span class="nt">&lt;/tr&gt;</span> <span class="nt">&lt;tr&gt;</span> <span class="nt">&lt;td</span> <span class="na">colspan=</span><span class="s">"2"</span><span class="nt">&gt;</span> <span class="nt">&lt;textarea</span> <span class="na">name=</span><span class="s">"comment"</span> <span class="na">rows=</span><span class="s">"10"</span><span class="nt">&gt;&lt;/textarea&gt;</span> <span class="nt">&lt;/td&gt;</span> <span class="nt">&lt;/tr&gt;</span> <span class="nt">&lt;tr&gt;</span> <span class="nt">&lt;td</span> <span class="na">colspan=</span><span class="s">"2"</span><span class="nt">&gt;</span> <span class="nt">&lt;input</span> <span class="na">class=</span><span class="s">"comment-submit"</span> <span class="na">type=</span><span class="s">"submit"</span> <span class="na">value=</span><span class="s">"Envoyer"</span> <span class="nt">/&gt;</span> <span class="nt">&lt;/td&gt;</span> <span class="nt">&lt;/tr&gt;</span> <span class="nt">&lt;/table&gt;</span> <span class="nt">&lt;/form&gt;</span></code></pre></figure> <p>Ce formulaire est affiché sous les commentaires existants.</p> <h2 id="traitement">Traitement</h2> <p>L’<code class="highlighter-rouge">action</code> du formulaire précédent pointait sur <a href="https://github.com/rom1v/blog.rom1v.com/blob/master/comments/submit.php"><code class="highlighter-rouge">comments/submit.php</code></a>. Il nous reste donc à écrire dans ce fichier le code à exécuter lorsqu’un utilisateur envoie un commentaire au serveur.</p> <p>Ce sera la seule partie “dynamique” du site.</p> <p>Voici les parties importantes de <a href="https://github.com/rom1v/blog.rom1v.com/blob/master/comments/submit.php"><code class="highlighter-rouge">comments/submit.php</code></a> (basé sur <a href="https://github.com/mpalmer/jekyll-static-comments/blob/master/commentsubmit.php">la version de Jekyll::StaticComments</a>) :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php"><span class="cp">&lt;?php</span> <span class="nv">$DATE_FORMAT</span> <span class="o">=</span> <span class="s2">"Y-m-d H:i:sP"</span><span class="p">;</span> <span class="nv">$EMAIL_ADDRESS</span> <span class="o">=</span> <span class="s2">"your@email"</span><span class="p">;</span> <span class="nv">$SUBJECT</span> <span class="o">=</span> <span class="s2">"Nouveau commentaire"</span><span class="p">;</span> <span class="nv">$COMMENT_SENT</span> <span class="o">=</span> <span class="s2">"sent.html"</span><span class="p">;</span> <span class="nv">$msg</span> <span class="o">=</span> <span class="s2">"post_id: "</span> <span class="o">.</span> <span class="nv">$_POST</span><span class="p">[</span><span class="s2">"post_id"</span><span class="p">]</span> <span class="o">.</span> <span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span> <span class="nv">$msg</span> <span class="o">.=</span> <span class="s2">"email: "</span> <span class="o">.</span> <span class="nv">$_POST</span><span class="p">[</span><span class="s2">"email"</span><span class="p">]</span> <span class="o">.</span> <span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span> <span class="nv">$msg</span> <span class="o">.=</span> <span class="s2">"---</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span> <span class="nv">$msg</span> <span class="o">.=</span> <span class="s2">"- id: ?</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span> <span class="nv">$msg</span> <span class="o">.=</span> <span class="s2">" author: "</span> <span class="o">.</span> <span class="nv">$_POST</span><span class="p">[</span><span class="s2">"name"</span><span class="p">]</span> <span class="o">.</span> <span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span> <span class="k">if</span> <span class="p">(</span><span class="nv">$_POST</span><span class="p">[</span><span class="s2">"url"</span><span class="p">]</span> <span class="o">!==</span> <span class="s1">''</span><span class="p">)</span> <span class="p">{</span> <span class="nv">$msg</span> <span class="o">.=</span> <span class="s2">" author-url: "</span> <span class="o">.</span> <span class="nv">$_POST</span><span class="p">[</span><span class="s2">"url"</span><span class="p">]</span> <span class="o">.</span> <span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span> <span class="p">}</span> <span class="nv">$msg</span> <span class="o">.=</span> <span class="s2">" date: "</span> <span class="o">.</span> <span class="nb">date</span><span class="p">(</span><span class="nv">$DATE_FORMAT</span><span class="p">)</span> <span class="o">.</span> <span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span> <span class="nv">$msg</span> <span class="o">.=</span> <span class="s2">" contents: |</span><span class="se">\n</span><span class="s2">"</span> <span class="o">.</span> <span class="nv">$_POST</span><span class="p">[</span><span class="s2">"comment"</span><span class="p">];</span> <span class="nv">$headers</span> <span class="o">=</span> <span class="s2">"From: </span><span class="nv">$EMAIL_ADDRESS</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span> <span class="nv">$headers</span> <span class="o">.=</span> <span class="s2">"Content-Type: text/plain; charset=utf-8"</span><span class="p">;</span> <span class="k">if</span> <span class="p">(</span><span class="nb">mail</span><span class="p">(</span><span class="nv">$EMAIL_ADDRESS</span><span class="p">,</span> <span class="nv">$SUBJECT</span><span class="p">,</span> <span class="nv">$msg</span><span class="p">,</span> <span class="nv">$headers</span><span class="p">))</span> <span class="p">{</span> <span class="k">include</span> <span class="nv">$COMMENT_SENT</span><span class="p">;</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="k">echo</span> <span class="s2">"Le commentaire n'a pas pu être envoyé."</span><span class="p">;</span> <span class="p">}</span></code></pre></figure> <p>Quand un commentaire est envoyé avec succès, la page <a href="https://github.com/rom1v/blog.rom1v.com/blob/master/comments/sent.html"><code class="highlighter-rouge">comments/sent.html</code></a> est affichée à l’utilisateur.</p> <p>Ainsi, lorsqu’un commentaire est posté, je reçois un mail :</p> <div class="highlighter-rouge"><pre class="codehilite"><code>post_id: /2017/01/commentaires-statiques-avec-jekyll email: my@email --- - id: ? author: ®om author-url: http://blog.rom1v.com date: 2017-01-09 19:27:10+01:00 contents: | Ceci est un test. </code></pre></div> <p>J’ai d’ailleurs ajouté une règle <a href="/2010/01/trier-ses-mails-directement-sur-le-serveur-procmail/">procmail</a> pour que ces mails arrivent dans un dossier dédié.</p> <p>Je peux alors copier le contenu dans le <code class="highlighter-rouge">.yaml</code> correspondant, formatter le commentaire (entre autres l’indenter de 4 espaces, ce qu’on pourrait automatiser), et le commiter.</p> <h2 id="rsum">Résumé</h2> <p>Une fois mis en place, vous devriez donc avoir les fichiers suivants :</p> <ul> <li><code class="highlighter-rouge">_data/comments-*.yaml</code></li> <li><code class="highlighter-rouge">_include/comments.html</code></li> <li><code class="highlighter-rouge">comments/submit.php</code></li> <li><code class="highlighter-rouge">comments/sent.html</code></li> </ul> <h2 id="conclusion">Conclusion</h2> <p>Je souhaitais depuis longtemps migrer vers un moteur de blog statique, qui correspond davantage à ma façon d’écrire des articles, et offre beaucoup d’avantages (légèreté, sécurité, maintenance…).</p> <p>Je suis très content d’y être parvenu sans perdre les commentaires ni la possibilité d’en poster de nouveaux.</p> <p>Certes, la validation est très manuelle, mais c’est le prix à payer pour avoir des commentaires statiques. Pour un blog avec une fréquence de commentaires assez faible, je pense que ce n’est pas très gênant.</p> Challenge reverse engineering 2015-07-21T16:31:04+02:00 http://blog.rom1v.com/2015/07/challenge-reverse-engineering <p>Un collègue m’a envoyé récemment le lien vers <a href="http://www.nerd.nintendo.com/files/HireMe.cpp">ce challenge</a>, utilisé pour recruter chez <a href="http://www.nerd.nintendo.com/">NERD</a> (que je ne connais pas).</p> <p>Je vous le partage car je l’ai trouvé très intéressant. Le but est de trouver un <em>input</em> qui donne l’<em>output</em> attendu.</p> <p>Pour info, entre le moment où j’ai eu connaissance du problème et le moment où je l’ai résolu, il s’est écoulé 6 jours. Je n’ai pas travaillé dessus à plein temps, mais ne comptez pas le résoudre en 20 minutes ;-)</p> <p>Je ne publie ni ma réponse au résultat attendu, ni les explications (ce serait incorrect vis-à-vis des auteurs du challenge). Je fournis par contre à la fin l’<em>input</em> qui donne comme <em>output</em> mon e-mail, ce qui suffit à prouver que j’ai résolu le problème.</p> <p>Amusez-vous bien !</p> <figure class="highlight"><pre><code class="language-c" data-lang="c"><span class="cp">#include &lt;string.h&gt; </span> <span class="k">typedef</span> <span class="kt">unsigned</span> <span class="kt">char</span> <span class="n">u8</span><span class="p">;</span> <span class="k">typedef</span> <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">u32</span><span class="p">;</span> <span class="n">u8</span> <span class="n">confusion</span><span class="p">[</span><span class="mi">512</span><span class="p">]</span><span class="o">=</span><span class="p">{</span> <span class="mh">0xac</span><span class="p">,</span><span class="mh">0xd1</span><span class="p">,</span><span class="mh">0x25</span><span class="p">,</span><span class="mh">0x94</span><span class="p">,</span><span class="mh">0x1f</span><span class="p">,</span><span class="mh">0xb3</span><span class="p">,</span><span class="mh">0x33</span><span class="p">,</span><span class="mh">0x28</span><span class="p">,</span><span class="mh">0x7c</span><span class="p">,</span><span class="mh">0x2b</span><span class="p">,</span><span class="mh">0x17</span><span class="p">,</span><span class="mh">0xbc</span><span class="p">,</span><span class="mh">0xf6</span><span class="p">,</span><span class="mh">0xb0</span><span class="p">,</span><span class="mh">0x55</span><span class="p">,</span><span class="mh">0x5d</span><span class="p">,</span> <span class="mh">0x8f</span><span class="p">,</span><span class="mh">0xd2</span><span class="p">,</span><span class="mh">0x48</span><span class="p">,</span><span class="mh">0xd4</span><span class="p">,</span><span class="mh">0xd3</span><span class="p">,</span><span class="mh">0x78</span><span class="p">,</span><span class="mh">0x62</span><span class="p">,</span><span class="mh">0x1a</span><span class="p">,</span><span class="mh">0x02</span><span class="p">,</span><span class="mh">0xf2</span><span class="p">,</span><span class="mh">0x01</span><span class="p">,</span><span class="mh">0xc9</span><span class="p">,</span><span class="mh">0xaa</span><span class="p">,</span><span class="mh">0xf0</span><span class="p">,</span><span class="mh">0x83</span><span class="p">,</span><span class="mh">0x71</span><span class="p">,</span> <span class="mh">0x72</span><span class="p">,</span><span class="mh">0x4b</span><span class="p">,</span><span class="mh">0x6a</span><span class="p">,</span><span class="mh">0xe8</span><span class="p">,</span><span class="mh">0xe9</span><span class="p">,</span><span class="mh">0x42</span><span class="p">,</span><span class="mh">0xc0</span><span class="p">,</span><span class="mh">0x53</span><span class="p">,</span><span class="mh">0x63</span><span class="p">,</span><span class="mh">0x66</span><span class="p">,</span><span class="mh">0x13</span><span class="p">,</span><span class="mh">0x4a</span><span class="p">,</span><span class="mh">0xc1</span><span class="p">,</span><span class="mh">0x85</span><span class="p">,</span><span class="mh">0xcf</span><span class="p">,</span><span class="mh">0x0c</span><span class="p">,</span> <span class="mh">0x24</span><span class="p">,</span><span class="mh">0x76</span><span class="p">,</span><span class="mh">0xa5</span><span class="p">,</span><span class="mh">0x6e</span><span class="p">,</span><span class="mh">0xd7</span><span class="p">,</span><span class="mh">0xa1</span><span class="p">,</span><span class="mh">0xec</span><span class="p">,</span><span class="mh">0xc6</span><span class="p">,</span><span class="mh">0x04</span><span class="p">,</span><span class="mh">0xc2</span><span class="p">,</span><span class="mh">0xa2</span><span class="p">,</span><span class="mh">0x5c</span><span class="p">,</span><span class="mh">0x81</span><span class="p">,</span><span class="mh">0x92</span><span class="p">,</span><span class="mh">0x6c</span><span class="p">,</span><span class="mh">0xda</span><span class="p">,</span> <span class="mh">0xc6</span><span class="p">,</span><span class="mh">0x86</span><span class="p">,</span><span class="mh">0xba</span><span class="p">,</span><span class="mh">0x4d</span><span class="p">,</span><span class="mh">0x39</span><span class="p">,</span><span class="mh">0xa0</span><span class="p">,</span><span class="mh">0x0e</span><span class="p">,</span><span class="mh">0x8c</span><span class="p">,</span><span class="mh">0x8a</span><span class="p">,</span><span class="mh">0xd0</span><span class="p">,</span><span class="mh">0xfe</span><span class="p">,</span><span class="mh">0x59</span><span class="p">,</span><span class="mh">0x96</span><span class="p">,</span><span class="mh">0x49</span><span class="p">,</span><span class="mh">0xe6</span><span class="p">,</span><span class="mh">0xea</span><span class="p">,</span> <span class="mh">0x69</span><span class="p">,</span><span class="mh">0x30</span><span class="p">,</span><span class="mh">0x52</span><span class="p">,</span><span class="mh">0x1c</span><span class="p">,</span><span class="mh">0xe0</span><span class="p">,</span><span class="mh">0xb2</span><span class="p">,</span><span class="mh">0x05</span><span class="p">,</span><span class="mh">0x9b</span><span class="p">,</span><span class="mh">0x10</span><span class="p">,</span><span class="mh">0x03</span><span class="p">,</span><span class="mh">0xa8</span><span class="p">,</span><span class="mh">0x64</span><span class="p">,</span><span class="mh">0x51</span><span class="p">,</span><span class="mh">0x97</span><span class="p">,</span><span class="mh">0x02</span><span class="p">,</span><span class="mh">0x09</span><span class="p">,</span> <span class="mh">0x8e</span><span class="p">,</span><span class="mh">0xad</span><span class="p">,</span><span class="mh">0xf7</span><span class="p">,</span><span class="mh">0x36</span><span class="p">,</span><span class="mh">0x47</span><span class="p">,</span><span class="mh">0xab</span><span class="p">,</span><span class="mh">0xce</span><span class="p">,</span><span class="mh">0x7f</span><span class="p">,</span><span class="mh">0x56</span><span class="p">,</span><span class="mh">0xca</span><span class="p">,</span><span class="mh">0x00</span><span class="p">,</span><span class="mh">0xe3</span><span class="p">,</span><span class="mh">0xed</span><span class="p">,</span><span class="mh">0xf1</span><span class="p">,</span><span class="mh">0x38</span><span class="p">,</span><span class="mh">0xd8</span><span class="p">,</span> <span class="mh">0x26</span><span class="p">,</span><span class="mh">0x1c</span><span class="p">,</span><span class="mh">0xdc</span><span class="p">,</span><span class="mh">0x35</span><span class="p">,</span><span class="mh">0x91</span><span class="p">,</span><span class="mh">0x43</span><span class="p">,</span><span class="mh">0x2c</span><span class="p">,</span><span class="mh">0x74</span><span class="p">,</span><span class="mh">0xb4</span><span class="p">,</span><span class="mh">0x61</span><span class="p">,</span><span class="mh">0x9d</span><span class="p">,</span><span class="mh">0x5e</span><span class="p">,</span><span class="mh">0xe9</span><span class="p">,</span><span class="mh">0x4c</span><span class="p">,</span><span class="mh">0xbf</span><span class="p">,</span><span class="mh">0x77</span><span class="p">,</span> <span class="mh">0x16</span><span class="p">,</span><span class="mh">0x1e</span><span class="p">,</span><span class="mh">0x21</span><span class="p">,</span><span class="mh">0x1d</span><span class="p">,</span><span class="mh">0x2d</span><span class="p">,</span><span class="mh">0xa9</span><span class="p">,</span><span class="mh">0x95</span><span class="p">,</span><span class="mh">0xb8</span><span class="p">,</span><span class="mh">0xc3</span><span class="p">,</span><span class="mh">0x8d</span><span class="p">,</span><span class="mh">0xf8</span><span class="p">,</span><span class="mh">0xdb</span><span class="p">,</span><span class="mh">0x34</span><span class="p">,</span><span class="mh">0xe1</span><span class="p">,</span><span class="mh">0x84</span><span class="p">,</span><span class="mh">0xd6</span><span class="p">,</span> <span class="mh">0x0b</span><span class="p">,</span><span class="mh">0x23</span><span class="p">,</span><span class="mh">0x4e</span><span class="p">,</span><span class="mh">0xff</span><span class="p">,</span><span class="mh">0x3c</span><span class="p">,</span><span class="mh">0x54</span><span class="p">,</span><span class="mh">0xa7</span><span class="p">,</span><span class="mh">0x78</span><span class="p">,</span><span class="mh">0xa4</span><span class="p">,</span><span class="mh">0x89</span><span class="p">,</span><span class="mh">0x33</span><span class="p">,</span><span class="mh">0x6d</span><span class="p">,</span><span class="mh">0xfb</span><span class="p">,</span><span class="mh">0x79</span><span class="p">,</span><span class="mh">0x27</span><span class="p">,</span><span class="mh">0xc4</span><span class="p">,</span> <span class="mh">0xf9</span><span class="p">,</span><span class="mh">0x40</span><span class="p">,</span><span class="mh">0x41</span><span class="p">,</span><span class="mh">0xdf</span><span class="p">,</span><span class="mh">0xc5</span><span class="p">,</span><span class="mh">0x82</span><span class="p">,</span><span class="mh">0x93</span><span class="p">,</span><span class="mh">0xdd</span><span class="p">,</span><span class="mh">0xa6</span><span class="p">,</span><span class="mh">0xef</span><span class="p">,</span><span class="mh">0xcd</span><span class="p">,</span><span class="mh">0x8d</span><span class="p">,</span><span class="mh">0xa3</span><span class="p">,</span><span class="mh">0xae</span><span class="p">,</span><span class="mh">0x7a</span><span class="p">,</span><span class="mh">0xb6</span><span class="p">,</span> <span class="mh">0x2f</span><span class="p">,</span><span class="mh">0xfd</span><span class="p">,</span><span class="mh">0xbd</span><span class="p">,</span><span class="mh">0xe5</span><span class="p">,</span><span class="mh">0x98</span><span class="p">,</span><span class="mh">0x66</span><span class="p">,</span><span class="mh">0xf3</span><span class="p">,</span><span class="mh">0x4f</span><span class="p">,</span><span class="mh">0x57</span><span class="p">,</span><span class="mh">0x88</span><span class="p">,</span><span class="mh">0x90</span><span class="p">,</span><span class="mh">0x9c</span><span class="p">,</span><span class="mh">0x0a</span><span class="p">,</span><span class="mh">0x50</span><span class="p">,</span><span class="mh">0xe7</span><span class="p">,</span><span class="mh">0x15</span><span class="p">,</span> <span class="mh">0x7b</span><span class="p">,</span><span class="mh">0x58</span><span class="p">,</span><span class="mh">0xbc</span><span class="p">,</span><span class="mh">0x07</span><span class="p">,</span><span class="mh">0x68</span><span class="p">,</span><span class="mh">0x3a</span><span class="p">,</span><span class="mh">0x5f</span><span class="p">,</span><span class="mh">0xee</span><span class="p">,</span><span class="mh">0x32</span><span class="p">,</span><span class="mh">0x9f</span><span class="p">,</span><span class="mh">0xeb</span><span class="p">,</span><span class="mh">0xcc</span><span class="p">,</span><span class="mh">0x18</span><span class="p">,</span><span class="mh">0x8b</span><span class="p">,</span><span class="mh">0xe2</span><span class="p">,</span><span class="mh">0x57</span><span class="p">,</span> <span class="mh">0xb7</span><span class="p">,</span><span class="mh">0x49</span><span class="p">,</span><span class="mh">0x37</span><span class="p">,</span><span class="mh">0xde</span><span class="p">,</span><span class="mh">0xf5</span><span class="p">,</span><span class="mh">0x99</span><span class="p">,</span><span class="mh">0x67</span><span class="p">,</span><span class="mh">0x5b</span><span class="p">,</span><span class="mh">0x3b</span><span class="p">,</span><span class="mh">0xbb</span><span class="p">,</span><span class="mh">0x3d</span><span class="p">,</span><span class="mh">0xb5</span><span class="p">,</span><span class="mh">0x2d</span><span class="p">,</span><span class="mh">0x19</span><span class="p">,</span><span class="mh">0x2e</span><span class="p">,</span><span class="mh">0x0d</span><span class="p">,</span> <span class="mh">0x93</span><span class="p">,</span><span class="mh">0xfc</span><span class="p">,</span><span class="mh">0x7e</span><span class="p">,</span><span class="mh">0x06</span><span class="p">,</span><span class="mh">0x08</span><span class="p">,</span><span class="mh">0xbe</span><span class="p">,</span><span class="mh">0x3f</span><span class="p">,</span><span class="mh">0xd9</span><span class="p">,</span><span class="mh">0x2a</span><span class="p">,</span><span class="mh">0x70</span><span class="p">,</span><span class="mh">0x9a</span><span class="p">,</span><span class="mh">0xc8</span><span class="p">,</span><span class="mh">0x7d</span><span class="p">,</span><span class="mh">0xd8</span><span class="p">,</span><span class="mh">0x46</span><span class="p">,</span><span class="mh">0x65</span><span class="p">,</span> <span class="mh">0x22</span><span class="p">,</span><span class="mh">0xf4</span><span class="p">,</span><span class="mh">0xb9</span><span class="p">,</span><span class="mh">0xa2</span><span class="p">,</span><span class="mh">0x6f</span><span class="p">,</span><span class="mh">0x12</span><span class="p">,</span><span class="mh">0x1b</span><span class="p">,</span><span class="mh">0x14</span><span class="p">,</span><span class="mh">0x45</span><span class="p">,</span><span class="mh">0xc7</span><span class="p">,</span><span class="mh">0x87</span><span class="p">,</span><span class="mh">0x31</span><span class="p">,</span><span class="mh">0x60</span><span class="p">,</span><span class="mh">0x29</span><span class="p">,</span><span class="mh">0xf7</span><span class="p">,</span><span class="mh">0x73</span><span class="p">,</span> <span class="mh">0x2c</span><span class="p">,</span><span class="mh">0x97</span><span class="p">,</span><span class="mh">0x72</span><span class="p">,</span><span class="mh">0xcd</span><span class="p">,</span><span class="mh">0x89</span><span class="p">,</span><span class="mh">0xa6</span><span class="p">,</span><span class="mh">0x88</span><span class="p">,</span><span class="mh">0x4c</span><span class="p">,</span><span class="mh">0xe8</span><span class="p">,</span><span class="mh">0x83</span><span class="p">,</span><span class="mh">0xeb</span><span class="p">,</span><span class="mh">0x59</span><span class="p">,</span><span class="mh">0xca</span><span class="p">,</span><span class="mh">0x50</span><span class="p">,</span><span class="mh">0x3f</span><span class="p">,</span><span class="mh">0x27</span><span class="p">,</span> <span class="mh">0x4e</span><span class="p">,</span><span class="mh">0xae</span><span class="p">,</span><span class="mh">0x43</span><span class="p">,</span><span class="mh">0xd5</span><span class="p">,</span><span class="mh">0x6e</span><span class="p">,</span><span class="mh">0xd0</span><span class="p">,</span><span class="mh">0x99</span><span class="p">,</span><span class="mh">0x7b</span><span class="p">,</span><span class="mh">0x7c</span><span class="p">,</span><span class="mh">0x40</span><span class="p">,</span><span class="mh">0x0c</span><span class="p">,</span><span class="mh">0x52</span><span class="p">,</span><span class="mh">0x86</span><span class="p">,</span><span class="mh">0xc1</span><span class="p">,</span><span class="mh">0x46</span><span class="p">,</span><span class="mh">0x12</span><span class="p">,</span> <span class="mh">0x5a</span><span class="p">,</span><span class="mh">0x28</span><span class="p">,</span><span class="mh">0xa8</span><span class="p">,</span><span class="mh">0xbb</span><span class="p">,</span><span class="mh">0xcb</span><span class="p">,</span><span class="mh">0xf0</span><span class="p">,</span><span class="mh">0x11</span><span class="p">,</span><span class="mh">0x95</span><span class="p">,</span><span class="mh">0x26</span><span class="p">,</span><span class="mh">0x0d</span><span class="p">,</span><span class="mh">0x34</span><span class="p">,</span><span class="mh">0x66</span><span class="p">,</span><span class="mh">0x22</span><span class="p">,</span><span class="mh">0x18</span><span class="p">,</span><span class="mh">0x6f</span><span class="p">,</span><span class="mh">0x51</span><span class="p">,</span> <span class="mh">0x9b</span><span class="p">,</span><span class="mh">0x3b</span><span class="p">,</span><span class="mh">0xda</span><span class="p">,</span><span class="mh">0xec</span><span class="p">,</span><span class="mh">0x5e</span><span class="p">,</span><span class="mh">0x00</span><span class="p">,</span><span class="mh">0x2a</span><span class="p">,</span><span class="mh">0xf5</span><span class="p">,</span><span class="mh">0x8f</span><span class="p">,</span><span class="mh">0x61</span><span class="p">,</span><span class="mh">0xba</span><span class="p">,</span><span class="mh">0x96</span><span class="p">,</span><span class="mh">0xb3</span><span class="p">,</span><span class="mh">0xd1</span><span class="p">,</span><span class="mh">0x30</span><span class="p">,</span><span class="mh">0xdc</span><span class="p">,</span> <span class="mh">0x33</span><span class="p">,</span><span class="mh">0x75</span><span class="p">,</span><span class="mh">0xe9</span><span class="p">,</span><span class="mh">0x6d</span><span class="p">,</span><span class="mh">0xc8</span><span class="p">,</span><span class="mh">0xa1</span><span class="p">,</span><span class="mh">0x3a</span><span class="p">,</span><span class="mh">0x3e</span><span class="p">,</span><span class="mh">0x5f</span><span class="p">,</span><span class="mh">0x9d</span><span class="p">,</span><span class="mh">0xfd</span><span class="p">,</span><span class="mh">0xa9</span><span class="p">,</span><span class="mh">0x31</span><span class="p">,</span><span class="mh">0x9f</span><span class="p">,</span><span class="mh">0xaa</span><span class="p">,</span><span class="mh">0x85</span><span class="p">,</span> <span class="mh">0x2f</span><span class="p">,</span><span class="mh">0x92</span><span class="p">,</span><span class="mh">0xaf</span><span class="p">,</span><span class="mh">0x67</span><span class="p">,</span><span class="mh">0x78</span><span class="p">,</span><span class="mh">0xa5</span><span class="p">,</span><span class="mh">0xab</span><span class="p">,</span><span class="mh">0x03</span><span class="p">,</span><span class="mh">0x21</span><span class="p">,</span><span class="mh">0x4f</span><span class="p">,</span><span class="mh">0xb9</span><span class="p">,</span><span class="mh">0xad</span><span class="p">,</span><span class="mh">0xfe</span><span class="p">,</span><span class="mh">0xf3</span><span class="p">,</span><span class="mh">0x42</span><span class="p">,</span><span class="mh">0xfc</span><span class="p">,</span> <span class="mh">0x17</span><span class="p">,</span><span class="mh">0xd7</span><span class="p">,</span><span class="mh">0xee</span><span class="p">,</span><span class="mh">0xa3</span><span class="p">,</span><span class="mh">0xd8</span><span class="p">,</span><span class="mh">0x80</span><span class="p">,</span><span class="mh">0x14</span><span class="p">,</span><span class="mh">0x2e</span><span class="p">,</span><span class="mh">0xa0</span><span class="p">,</span><span class="mh">0x47</span><span class="p">,</span><span class="mh">0x55</span><span class="p">,</span><span class="mh">0xc4</span><span class="p">,</span><span class="mh">0xff</span><span class="p">,</span><span class="mh">0xe5</span><span class="p">,</span><span class="mh">0x13</span><span class="p">,</span><span class="mh">0x3f</span><span class="p">,</span> <span class="mh">0x81</span><span class="p">,</span><span class="mh">0xb6</span><span class="p">,</span><span class="mh">0x7a</span><span class="p">,</span><span class="mh">0x94</span><span class="p">,</span><span class="mh">0xd0</span><span class="p">,</span><span class="mh">0xb5</span><span class="p">,</span><span class="mh">0x54</span><span class="p">,</span><span class="mh">0xbf</span><span class="p">,</span><span class="mh">0x91</span><span class="p">,</span><span class="mh">0xa7</span><span class="p">,</span><span class="mh">0x37</span><span class="p">,</span><span class="mh">0xf1</span><span class="p">,</span><span class="mh">0x6b</span><span class="p">,</span><span class="mh">0xc9</span><span class="p">,</span><span class="mh">0x1b</span><span class="p">,</span><span class="mh">0xb1</span><span class="p">,</span> <span class="mh">0x3c</span><span class="p">,</span><span class="mh">0xb6</span><span class="p">,</span><span class="mh">0xd9</span><span class="p">,</span><span class="mh">0x32</span><span class="p">,</span><span class="mh">0x24</span><span class="p">,</span><span class="mh">0x8d</span><span class="p">,</span><span class="mh">0xf2</span><span class="p">,</span><span class="mh">0x82</span><span class="p">,</span><span class="mh">0xb4</span><span class="p">,</span><span class="mh">0xf9</span><span class="p">,</span><span class="mh">0xdb</span><span class="p">,</span><span class="mh">0x7d</span><span class="p">,</span><span class="mh">0x44</span><span class="p">,</span><span class="mh">0xfb</span><span class="p">,</span><span class="mh">0x1e</span><span class="p">,</span><span class="mh">0xd4</span><span class="p">,</span> <span class="mh">0xea</span><span class="p">,</span><span class="mh">0x5d</span><span class="p">,</span><span class="mh">0x35</span><span class="p">,</span><span class="mh">0x69</span><span class="p">,</span><span class="mh">0x23</span><span class="p">,</span><span class="mh">0x71</span><span class="p">,</span><span class="mh">0x57</span><span class="p">,</span><span class="mh">0x01</span><span class="p">,</span><span class="mh">0x06</span><span class="p">,</span><span class="mh">0xe4</span><span class="p">,</span><span class="mh">0x55</span><span class="p">,</span><span class="mh">0x9a</span><span class="p">,</span><span class="mh">0xa4</span><span class="p">,</span><span class="mh">0x58</span><span class="p">,</span><span class="mh">0x56</span><span class="p">,</span><span class="mh">0xc7</span><span class="p">,</span> <span class="mh">0x4a</span><span class="p">,</span><span class="mh">0x8c</span><span class="p">,</span><span class="mh">0x8a</span><span class="p">,</span><span class="mh">0xd6</span><span class="p">,</span><span class="mh">0x6a</span><span class="p">,</span><span class="mh">0x49</span><span class="p">,</span><span class="mh">0x70</span><span class="p">,</span><span class="mh">0xc5</span><span class="p">,</span><span class="mh">0x8e</span><span class="p">,</span><span class="mh">0x0a</span><span class="p">,</span><span class="mh">0x62</span><span class="p">,</span><span class="mh">0xdc</span><span class="p">,</span><span class="mh">0x29</span><span class="p">,</span><span class="mh">0x4b</span><span class="p">,</span><span class="mh">0x42</span><span class="p">,</span><span class="mh">0x41</span><span class="p">,</span> <span class="mh">0xcb</span><span class="p">,</span><span class="mh">0x2b</span><span class="p">,</span><span class="mh">0xb7</span><span class="p">,</span><span class="mh">0xce</span><span class="p">,</span><span class="mh">0x08</span><span class="p">,</span><span class="mh">0xa1</span><span class="p">,</span><span class="mh">0x76</span><span class="p">,</span><span class="mh">0x1d</span><span class="p">,</span><span class="mh">0x1a</span><span class="p">,</span><span class="mh">0xb8</span><span class="p">,</span><span class="mh">0xe3</span><span class="p">,</span><span class="mh">0xcc</span><span class="p">,</span><span class="mh">0x7e</span><span class="p">,</span><span class="mh">0x48</span><span class="p">,</span><span class="mh">0x20</span><span class="p">,</span><span class="mh">0xe6</span><span class="p">,</span> <span class="mh">0xf8</span><span class="p">,</span><span class="mh">0x45</span><span class="p">,</span><span class="mh">0x93</span><span class="p">,</span><span class="mh">0xde</span><span class="p">,</span><span class="mh">0xc3</span><span class="p">,</span><span class="mh">0x63</span><span class="p">,</span><span class="mh">0x0f</span><span class="p">,</span><span class="mh">0xb0</span><span class="p">,</span><span class="mh">0xac</span><span class="p">,</span><span class="mh">0x5c</span><span class="p">,</span><span class="mh">0xba</span><span class="p">,</span><span class="mh">0xdf</span><span class="p">,</span><span class="mh">0x07</span><span class="p">,</span><span class="mh">0x77</span><span class="p">,</span><span class="mh">0xe7</span><span class="p">,</span><span class="mh">0x4e</span><span class="p">,</span> <span class="mh">0x1f</span><span class="p">,</span><span class="mh">0x28</span><span class="p">,</span><span class="mh">0x10</span><span class="p">,</span><span class="mh">0x6c</span><span class="p">,</span><span class="mh">0x59</span><span class="p">,</span><span class="mh">0xd3</span><span class="p">,</span><span class="mh">0xdd</span><span class="p">,</span><span class="mh">0x2d</span><span class="p">,</span><span class="mh">0x65</span><span class="p">,</span><span class="mh">0x39</span><span class="p">,</span><span class="mh">0xb2</span><span class="p">,</span><span class="mh">0x74</span><span class="p">,</span><span class="mh">0x84</span><span class="p">,</span><span class="mh">0x3d</span><span class="p">,</span><span class="mh">0xf4</span><span class="p">,</span><span class="mh">0xbd</span><span class="p">,</span> <span class="mh">0xc7</span><span class="p">,</span><span class="mh">0x79</span><span class="p">,</span><span class="mh">0x60</span><span class="p">,</span><span class="mh">0x0b</span><span class="p">,</span><span class="mh">0x4d</span><span class="p">,</span><span class="mh">0x33</span><span class="p">,</span><span class="mh">0x36</span><span class="p">,</span><span class="mh">0x25</span><span class="p">,</span><span class="mh">0xbc</span><span class="p">,</span><span class="mh">0xe0</span><span class="p">,</span><span class="mh">0x09</span><span class="p">,</span><span class="mh">0xcf</span><span class="p">,</span><span class="mh">0x5b</span><span class="p">,</span><span class="mh">0xe2</span><span class="p">,</span><span class="mh">0x38</span><span class="p">,</span><span class="mh">0x9e</span><span class="p">,</span> <span class="mh">0xc0</span><span class="p">,</span><span class="mh">0xef</span><span class="p">,</span><span class="mh">0xd2</span><span class="p">,</span><span class="mh">0x16</span><span class="p">,</span><span class="mh">0x05</span><span class="p">,</span><span class="mh">0xbe</span><span class="p">,</span><span class="mh">0x53</span><span class="p">,</span><span class="mh">0xf7</span><span class="p">,</span><span class="mh">0xc2</span><span class="p">,</span><span class="mh">0xc6</span><span class="p">,</span><span class="mh">0xa2</span><span class="p">,</span><span class="mh">0x24</span><span class="p">,</span><span class="mh">0x98</span><span class="p">,</span><span class="mh">0x1c</span><span class="p">,</span><span class="mh">0xad</span><span class="p">,</span><span class="mh">0x04</span><span class="p">};</span> <span class="n">u32</span> <span class="n">diffusion</span><span class="p">[</span><span class="mi">32</span><span class="p">]</span><span class="o">=</span><span class="p">{</span> <span class="mh">0xf26cb481</span><span class="p">,</span><span class="mh">0x16a5dc92</span><span class="p">,</span><span class="mh">0x3c5ba924</span><span class="p">,</span><span class="mh">0x79b65248</span><span class="p">,</span><span class="mh">0x2fc64b18</span><span class="p">,</span><span class="mh">0x615acd29</span><span class="p">,</span><span class="mh">0xc3b59a42</span><span class="p">,</span><span class="mh">0x976b2584</span><span class="p">,</span> <span class="mh">0x6cf281b4</span><span class="p">,</span><span class="mh">0xa51692dc</span><span class="p">,</span><span class="mh">0x5b3c24a9</span><span class="p">,</span><span class="mh">0xb6794852</span><span class="p">,</span><span class="mh">0xc62f184b</span><span class="p">,</span><span class="mh">0x5a6129cd</span><span class="p">,</span><span class="mh">0xb5c3429a</span><span class="p">,</span><span class="mh">0x6b978425</span><span class="p">,</span> <span class="mh">0xb481f26c</span><span class="p">,</span><span class="mh">0xdc9216a5</span><span class="p">,</span><span class="mh">0xa9243c5b</span><span class="p">,</span><span class="mh">0x524879b6</span><span class="p">,</span><span class="mh">0x4b182fc6</span><span class="p">,</span><span class="mh">0xcd29615a</span><span class="p">,</span><span class="mh">0x9a42c3b5</span><span class="p">,</span><span class="mh">0x2584976b</span><span class="p">,</span> <span class="mh">0x81b46cf2</span><span class="p">,</span><span class="mh">0x92dca516</span><span class="p">,</span><span class="mh">0x24a95b3c</span><span class="p">,</span><span class="mh">0x4852b679</span><span class="p">,</span><span class="mh">0x184bc62f</span><span class="p">,</span><span class="mh">0x29cd5a61</span><span class="p">,</span><span class="mh">0x429ab5c3</span><span class="p">,</span><span class="mh">0x84256b97</span><span class="p">};</span> <span class="n">u8</span> <span class="n">input</span><span class="p">[</span><span class="mi">32</span><span class="p">]</span><span class="o">=</span><span class="p">{</span> <span class="c1">//change only this : </span><span class="mh">0x66</span><span class="p">,</span><span class="mh">0xd5</span><span class="p">,</span><span class="mh">0x4e</span><span class="p">,</span><span class="mh">0x28</span><span class="p">,</span><span class="mh">0x5f</span><span class="p">,</span><span class="mh">0xff</span><span class="p">,</span><span class="mh">0x6b</span><span class="p">,</span><span class="mh">0x53</span><span class="p">,</span><span class="mh">0xac</span><span class="p">,</span><span class="mh">0x3b</span><span class="p">,</span><span class="mh">0x34</span><span class="p">,</span><span class="mh">0x14</span><span class="p">,</span><span class="mh">0xb5</span><span class="p">,</span><span class="mh">0x3c</span><span class="p">,</span><span class="mh">0xb2</span><span class="p">,</span><span class="mh">0xc6</span><span class="p">,</span> <span class="mh">0xa4</span><span class="p">,</span><span class="mh">0x85</span><span class="p">,</span><span class="mh">0x1e</span><span class="p">,</span><span class="mh">0x0d</span><span class="p">,</span><span class="mh">0x86</span><span class="p">,</span><span class="mh">0xc7</span><span class="p">,</span><span class="mh">0x4f</span><span class="p">,</span><span class="mh">0xba</span><span class="p">,</span><span class="mh">0x75</span><span class="p">,</span><span class="mh">0x5e</span><span class="p">,</span><span class="mh">0xcb</span><span class="p">,</span><span class="mh">0xc3</span><span class="p">,</span><span class="mh">0x6e</span><span class="p">,</span><span class="mh">0x48</span><span class="p">,</span><span class="mh">0x79</span><span class="p">,</span><span class="mh">0x8f</span> <span class="c1">// </span><span class="p">};</span> <span class="kt">void</span> <span class="nf">Forward</span><span class="p">(</span><span class="n">u8</span> <span class="n">c</span><span class="p">[</span><span class="mi">32</span><span class="p">],</span><span class="n">u8</span> <span class="n">d</span><span class="p">[</span><span class="mi">32</span><span class="p">],</span><span class="n">u8</span> <span class="n">s</span><span class="p">[</span><span class="mi">512</span><span class="p">],</span><span class="n">u32</span> <span class="n">p</span><span class="p">[</span><span class="mi">32</span><span class="p">])</span> <span class="p">{</span> <span class="k">for</span><span class="p">(</span><span class="n">u32</span> <span class="n">i</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="n">i</span><span class="o">&lt;</span><span class="mi">256</span><span class="p">;</span><span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span> <span class="k">for</span><span class="p">(</span><span class="n">u8</span> <span class="n">j</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="n">j</span><span class="o">&lt;</span><span class="mi">32</span><span class="p">;</span><span class="n">j</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span> <span class="n">d</span><span class="p">[</span><span class="n">j</span><span class="p">]</span><span class="o">=</span><span class="n">s</span><span class="p">[</span><span class="n">c</span><span class="p">[</span><span class="n">j</span><span class="p">]];</span> <span class="n">c</span><span class="p">[</span><span class="n">j</span><span class="p">]</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span> <span class="p">}</span> <span class="k">for</span><span class="p">(</span><span class="n">u8</span> <span class="n">j</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="n">j</span><span class="o">&lt;</span><span class="mi">32</span><span class="p">;</span><span class="n">j</span><span class="o">++</span><span class="p">)</span> <span class="k">for</span><span class="p">(</span><span class="n">u8</span> <span class="n">k</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="n">k</span><span class="o">&lt;</span><span class="mi">32</span><span class="p">;</span><span class="n">k</span><span class="o">++</span><span class="p">)</span> <span class="n">c</span><span class="p">[</span><span class="n">j</span><span class="p">]</span><span class="o">^=</span><span class="n">d</span><span class="p">[</span><span class="n">k</span><span class="p">]</span><span class="o">*</span><span class="p">((</span><span class="n">p</span><span class="p">[</span><span class="n">j</span><span class="p">]</span><span class="o">&gt;&gt;</span><span class="n">k</span><span class="p">)</span><span class="o">&amp;</span><span class="mi">1</span><span class="p">);</span> <span class="p">}</span> <span class="k">for</span><span class="p">(</span><span class="n">u8</span> <span class="n">i</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="n">i</span><span class="o">&lt;</span><span class="mi">16</span><span class="p">;</span><span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="n">d</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="o">=</span><span class="n">s</span><span class="p">[</span><span class="n">c</span><span class="p">[</span><span class="n">i</span><span class="o">*</span><span class="mi">2</span><span class="p">]]</span><span class="o">^</span><span class="n">s</span><span class="p">[</span><span class="n">c</span><span class="p">[</span><span class="n">i</span><span class="o">*</span><span class="mi">2</span><span class="o">+</span><span class="mi">1</span><span class="p">]</span><span class="o">+</span><span class="mi">256</span><span class="p">];</span> <span class="p">}</span> <span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span><span class="o">*</span> <span class="n">argv</span><span class="p">[])</span> <span class="p">{</span> <span class="n">u8</span> <span class="n">target</span><span class="p">[]</span><span class="o">=</span><span class="s">"Hire me!!!!!!!!"</span><span class="p">;</span> <span class="n">u8</span> <span class="n">output</span><span class="p">[</span><span class="mi">32</span><span class="p">];</span> <span class="n">Forward</span><span class="p">(</span><span class="n">input</span><span class="p">,</span><span class="n">output</span><span class="p">,</span><span class="n">confusion</span><span class="p">,</span><span class="n">diffusion</span><span class="p">);</span> <span class="k">return</span> <span class="n">memcmp</span><span class="p">(</span><span class="n">output</span><span class="p">,</span><span class="n">target</span><span class="p">,</span><span class="mi">16</span><span class="p">);</span> <span class="c1">// =&gt; contact jobs(at)nerd.nintendo.com </span><span class="p">}</span></code></pre></figure> <p>Ma preuve de solution :</p> <figure class="highlight"><pre><code class="language-diff" data-lang="diff"><span class="gh">diff --git a/HireMe.cpp b/HireMe.cpp index ca94719..8374683 100644 </span><span class="gd">--- a/HireMe.cpp </span><span class="gi">+++ b/HireMe.cpp </span><span class="gu">@@ -45,8 +45,8 @@ u32 diffusion[32]={ </span> u8 input[32]={ //change only this : <span class="gd">-0x66,0xd5,0x4e,0x28,0x5f,0xff,0x6b,0x53,0xac,0x3b,0x34,0x14,0xb5,0x3c,0xb2,0xc6, -0xa4,0x85,0x1e,0x0d,0x86,0xc7,0x4f,0xba,0x75,0x5e,0xcb,0xc3,0x6e,0x48,0x79,0x8f </span><span class="gi">+0x82,0x69,0xd7,0x3c,0xd7,0x58,0xd7,0x0d,0x22,0x46,0x58,0x22,0x22,0x69,0x22,0x77, +0x77,0xd7,0x77,0xe6,0xf8,0x22,0xd7,0x58,0x9c,0x58,0x3c,0xf8,0xf8,0x22,0x58,0x13 </span> // }; <span class="gu">@@ -70,7 +70,7 @@ void Forward(u8 c[32],u8 d[32],u8 s[512],u32 p[32]) </span> int main(int argc, char* argv[]) { <span class="gd">- u8 target[]="Hire me!!!!!!!!"; </span><span class="gi">+ u8 target[]="[rom@rom1v.com]"; </span> u8 output[32]; Forward(input,output,confusion,diffusion);</code></pre></figure> Exécuter un algorithme lors de la compilation (templates C++) 2015-03-27T19:53:08+01:00 http://blog.rom1v.com/2015/03/executer-un-algorithme-lors-de-la-compilation-templates-c <p class="center"><img src="/assets/metahanoi/hanoi.jpg" alt="hanoi" /></p> <p>En général, pour résoudre un problème donné, nous écrivons un algorithme dans un langage source, puis nous le compilons (dans le cas d’un langage compilé). La compilation consiste à traduire le code source en un code exécutable par une machine cible. C’est lors de l’exécution de ce fichier que l’algorithme est déroulé.</p> <p>Mais certains langages, en l’occurrence C++, proposent des mécanismes permettant un certain contrôle sur la phase de compilation, tellement expressifs qu’ils permettent la <a href="https://fr.wikipedia.org/wiki/M%C3%A9taprogrammation">métaprogrammation</a>. Nous pouvons alors faire exécuter un algorithme directement par le compilateur, qui se contente de produire un fichier exécutable affichant le résultat.</p> <p>À titre d’exemple, je vais présenter dans ce billet comment résoudre le problème des <a href="https://fr.wikipedia.org/wiki/Tours_de_Hano%C3%AF">tours de Hanoï</a> (généralisé, c’est-à-dire quelque soit la position initiale des disques) lors de la phase de compilation.</p> <p>Les programmes complets décrits dans ce billet sont <em>gittés</em> :</p> <div class="highlighter-rouge"><pre class="codehilite"><code>git clone http://git.rom1v.com/metahanoi.git </code></pre></div> <p>(ou sur <a href="https://github.com/rom1v/metahanoi.git">github</a>)</p> <h2 id="problme-des-tours-de-hano-gnralis">Problème des tours de Hanoï généralisé</h2> <p>La résolution naturelle du problème <a href="https://fr.wikipedia.org/wiki/Tours_de_Hano%C3%AF#Algorithme_g.C3.A9n.C3.A9ralis.C3.A9_.C3.A0_une_position_quelconque">généralisé</a> des tours de Hanoï est récursive.</p> <p>Pour déplacer <em>n</em> disques vers la tour <em>T</em>, il faut:</p> <ol> <li>déterminer sur quelle tour <em>S</em> se trouve le plus grand des <em>n</em> disques ;</li> <li>déplacer les <em>n-1</em> premiers disques sur la tour intermédiaire <em>I</em> (ni <em>S</em> ni <em>T</em>) ;</li> <li>déplacer le plus grand disque de <em>S</em> vers <em>T</em> ;</li> <li>redéplacer les <em>n-1</em> premiers disques vers <em>I</em> vers <em>T</em>.</li> </ol> <p>En voici une implémentation <em>classique</em> en C++ (le compilateur va générer le code permettant d’exécuter l’algorithme) :</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="cp">#include &lt;iterator&gt; #include &lt;iostream&gt; #include &lt;vector&gt; </span> <span class="k">class</span> <span class="nc">Hanoi</span> <span class="p">{</span> <span class="k">using</span> <span class="n">tower</span> <span class="o">=</span> <span class="kt">unsigned</span> <span class="kt">int</span><span class="p">;</span> <span class="k">using</span> <span class="n">size</span> <span class="o">=</span> <span class="kt">unsigned</span> <span class="kt">int</span><span class="p">;</span> <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="n">tower</span><span class="o">&gt;</span> <span class="n">state</span><span class="p">;</span> <span class="c1">// disk number i is on tower state[i] </span> <span class="k">public</span><span class="o">:</span> <span class="n">Hanoi</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">initializer_list</span><span class="o">&lt;</span><span class="n">tower</span><span class="o">&gt;</span> <span class="n">init</span><span class="p">)</span> <span class="o">:</span> <span class="n">state</span><span class="p">(</span><span class="n">init</span><span class="p">)</span> <span class="p">{}</span> <span class="kt">void</span> <span class="n">solve</span><span class="p">(</span><span class="n">tower</span> <span class="n">target</span><span class="p">)</span> <span class="p">{</span> <span class="n">printState</span><span class="p">();</span> <span class="c1">// initial state </span> <span class="n">solveRec</span><span class="p">(</span><span class="n">state</span><span class="p">.</span><span class="n">size</span><span class="p">(),</span> <span class="n">target</span><span class="p">);</span> <span class="p">}</span> <span class="k">private</span><span class="o">:</span> <span class="kt">void</span> <span class="n">solveRec</span><span class="p">(</span><span class="n">size</span> <span class="n">disks</span><span class="p">,</span> <span class="n">tower</span> <span class="n">target</span><span class="p">)</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="n">disks</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span><span class="p">;</span> <span class="p">}</span> <span class="c1">// the tower of the largest disk at this depth of recursion </span> <span class="n">tower</span> <span class="o">&amp;</span><span class="n">largest</span> <span class="o">=</span> <span class="n">state</span><span class="p">[</span><span class="n">state</span><span class="p">.</span><span class="n">size</span><span class="p">()</span> <span class="o">-</span> <span class="n">disks</span><span class="p">];</span> <span class="k">if</span> <span class="p">(</span><span class="n">largest</span> <span class="o">==</span> <span class="n">target</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// the largest disk is already on the target tower </span> <span class="n">solveRec</span><span class="p">(</span><span class="n">disks</span> <span class="o">-</span> <span class="mi">1</span><span class="p">,</span> <span class="n">target</span><span class="p">);</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="c1">// move disks above the largest to the intermediate tower </span> <span class="n">solveRec</span><span class="p">(</span><span class="n">disks</span> <span class="o">-</span> <span class="mi">1</span><span class="p">,</span> <span class="n">other</span><span class="p">(</span><span class="n">largest</span><span class="p">,</span> <span class="n">target</span><span class="p">));</span> <span class="c1">// move the largest disk to the target </span> <span class="n">largest</span> <span class="o">=</span> <span class="n">target</span><span class="p">;</span> <span class="n">printState</span><span class="p">();</span> <span class="c1">// move back the disks on the largest </span> <span class="n">solveRec</span><span class="p">(</span><span class="n">disks</span> <span class="o">-</span> <span class="mi">1</span><span class="p">,</span> <span class="n">target</span><span class="p">);</span> <span class="p">}</span> <span class="p">}</span> <span class="kt">void</span> <span class="n">printState</span><span class="p">()</span> <span class="p">{</span> <span class="n">std</span><span class="o">::</span><span class="n">copy</span><span class="p">(</span><span class="n">state</span><span class="p">.</span><span class="n">cbegin</span><span class="p">(),</span> <span class="n">state</span><span class="p">.</span><span class="n">cend</span><span class="p">(),</span> <span class="n">std</span><span class="o">::</span><span class="n">ostream_iterator</span><span class="o">&lt;</span><span class="n">tower</span><span class="o">&gt;</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">cout</span><span class="p">));</span> <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span> <span class="p">}</span> <span class="k">static</span> <span class="kr">inline</span> <span class="n">tower</span> <span class="n">other</span><span class="p">(</span><span class="n">tower</span> <span class="n">t1</span><span class="p">,</span> <span class="n">tower</span> <span class="n">t2</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="mi">3</span> <span class="o">-</span> <span class="n">t1</span> <span class="o">-</span> <span class="n">t2</span><span class="p">;</span> <span class="p">}</span> <span class="p">};</span> <span class="kt">int</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span> <span class="n">Hanoi</span><span class="p">{</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span> <span class="p">}.</span><span class="n">solve</span><span class="p">(</span><span class="mi">2</span><span class="p">);</span> <span class="k">return</span> <span class="mi">0</span><span class="p">;</span> <span class="p">}</span></code></pre></figure> <p>(<a href="https://github.com/rom1v/metahanoi/blob/runtime/hanoi.cpp">source</a> – <a href="https://github.com/rom1v/metahanoi/commit/runtime">commit</a> – tag <code class="highlighter-rouge">runtime</code>)</p> <p>À compiler avec :</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash">g++ -std<span class="o">=</span>c++11 hanoi.cpp -o hanoi</code></pre></figure> <p>L’algorithme utilise un simple vecteur de positions des disques, indexés du plus grand au plus petit, pour stocker l’état courant.</p> <p>Par exemple, l’état <code class="highlighter-rouge"><span class="p">{</span><span class="w"> </span><span class="err">0,</span><span class="w"> </span><span class="err">0,</span><span class="w"> </span><span class="err">0,</span><span class="w"> </span><span class="err">0</span><span class="w"> </span><span class="p">}</span></code> représente 4 disques sur la tour 0 :</p> <div class="highlighter-rouge"><pre class="codehilite"><code>state = { 0, 0, 0, 0 }; 0 1 2 | | | -+- | | --+-- | | ---+--- | | ----+---- | | </code></pre></div> <p>L’état <code class="highlighter-rouge"><span class="p">{</span><span class="w"> </span><span class="err">1,</span><span class="w"> </span><span class="err">1,</span><span class="w"> </span><span class="err">0,</span><span class="w"> </span><span class="err">2</span><span class="w"> </span><span class="p">}</span></code>, quant-à lui, représente ces positions:</p> <div class="highlighter-rouge"><pre class="codehilite"><code>state = { 1, 1, 2, 1 }; 0 1 2 | | | | | | | -+- | | ---+--- | | ----+---- --+-- </code></pre></div> <p>Dans cette version, pour déplacer le disque <code class="highlighter-rouge">i</code>, il suffit alors de changer la valeur de <code class="highlighter-rouge">state[i]</code>.</p> <p>Il serait possible d’utiliser une structure de données différente, comme un vecteur par tour stockant les numéros des disques présents, mais celle que j’ai choisie sera beaucoup plus adaptée pour la suite.</p> <p>Étant données 2 tours <em>T1</em> et <em>T2</em>, il est très facile d’en déduire la 3e : <em>3 - T1 - T2</em>. Ce calcul est extrait dans la fonction <em>inlinée</em> <code class="highlighter-rouge">other(…)</code>.</p> <p>Le contexte étant présenté, essayons maintenant d’implémenter le même algorithme pour le faire exécuter par le compilateur.</p> <h2 id="structure-de-donnes-constante">Structure de données constante</h2> <p><code class="highlighter-rouge">std::vector</code> est une structure de données utilisable uniquement à l’exécution : il n’est pas possible d’en créer ou d’en utiliser une instance lors de la compilation. Même l’utilisation d’un simple tableau d’entiers nous poserait des problèmes par la suite.</p> <p>Nous allons donc grandement simplifier le stockage des positions des disques, <strong>en utilisant un seul entier</strong>. En effet, à chaque disque est associée une tour qui peut être 0, 1 ou 2. Par conséquent, un chiffre en <a href="https://fr.wikipedia.org/wiki/Syst%C3%A8me_trinaire">base 3</a> (un <em>trit</em>) suffit pour stocker la position d’une tour.</p> <p>Ainsi, nous pouvons représenter l’état <code class="highlighter-rouge"><span class="p">{</span><span class="w"> </span><span class="err">1,</span><span class="w"> </span><span class="err">2,</span><span class="w"> </span><span class="err">1,</span><span class="w"> </span><span class="err">0,</span><span class="w"> </span><span class="err">2</span><span class="w"> </span><span class="p">}</span></code> par l’entier 146 <em>(1×81 + 2×27 + 1×9 + 0×3 + 2×1)</em> :</p> <table> <thead> <tr> <th style="text-align: center">3<sup>4</sup></th> <th style="text-align: center">3<sup>3</sup></th> <th style="text-align: center">3<sup>2</sup></th> <th style="text-align: center">3<sup>1</sup></th> <th style="text-align: center">3<sup>0</sup></th> </tr> </thead> <tbody> <tr> <td style="text-align: center">1</td> <td style="text-align: center">2</td> <td style="text-align: center">1</td> <td style="text-align: center">0</td> <td style="text-align: center">2</td> </tr> </tbody> </table> <p>Au passage, voici comment convertir rapidement un nombre dans une autre base en <em>shell</em> (pratique pour débugger) :</p> <div class="highlighter-rouge"><pre class="codehilite"><code>$ echo 'ibase=3; 12102' | bc 146 $ echo 'obase=3; 146' | bc 12102 </code></pre></div> <p>Nous allons utiliser le type entier le plus long, à savoir <a href="https://fr.wikipedia.org/wiki/C_%28langage%29#Types"><code class="highlighter-rouge">unsigned long long</code></a>, stockant au minimum 64 bits, soit 64 digits en base 2. En base 3, cela représente <em>64 × ln(2)/ln(3) ≃</em> 40 digits : nous pouvons donc stocker la position de 40 disques dans un seul entier.</p> <p>Pour cela, définissons un type <code class="highlighter-rouge">state</code> :</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="k">using</span> <span class="n">state</span> <span class="o">=</span> <span class="kt">unsigned</span> <span class="kt">long</span> <span class="kt">long</span><span class="p">;</span></code></pre></figure> <h2 id="expressions-constantes-gnralises">Expressions constantes généralisées</h2> <p>Nous allons écrire une première ébauche n’utilisant que des <a href="http://www.cprogramming.com/c++11/c++11-compile-time-processing-with-constexpr.html">expressions constantes</a>, clairement indiquées et vérifiées depuis C++11 grâce au mot-clé <a href="http://en.cppreference.com/w/cpp/language/constexpr"><code class="highlighter-rouge">constexpr</code></a>. Une fonction <code class="highlighter-rouge">constexpr</code> doit, en gros, n’être composé que d’une instruction <code class="highlighter-rouge">return</code>.</p> <p>C’est le cas à l’évidence pour notre fonction <code class="highlighter-rouge">other(…)</code> :</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="k">constexpr</span> <span class="n">tower</span> <span class="nf">other</span><span class="p">(</span><span class="n">tower</span> <span class="n">t1</span><span class="p">,</span> <span class="n">tower</span> <span class="n">t2</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="mi">3</span> <span class="o">-</span> <span class="n">t1</span> <span class="o">-</span> <span class="n">t2</span><span class="p">;</span> <span class="p">}</span></code></pre></figure> <p>Grâce à l’<a href="https://en.wikipedia.org/wiki/%3F:">opérateur ternaire</a> et à la <a href="https://fr.wikipedia.org/wiki/R%C3%A9cursivit%C3%A9#R.C3.A9cursivit.C3.A9_en_informatique_et_en_logique">récursivité</a>, nous pouvons cependant en écrire de plus complexes.</p> <p>Dans notre programme classique, le déplacement d’un disque <em>i</em> se résumait à changer la valeur de <code class="highlighter-rouge">state[i]</code>. Maintenant que l’état du système est compacté dans un seul entier, l’opération est moins directe.</p> <p>Soit l’état courant <code class="highlighter-rouge"><span class="p">{</span><span class="w"> </span><span class="err">0,</span><span class="w"> </span><span class="err">1,</span><span class="w"> </span><span class="err">2,</span><span class="w"> </span><span class="err">0,</span><span class="w"> </span><span class="err">0</span><span class="w"> </span><span class="p">}</span></code> (ou plus simplement <code class="highlighter-rouge">01200</code>). Supposons que nous souhaitions déplacer le disque au sommet de la tour 2 vers la tour 1. Cela consiste, en fait, à remplacer le dernier <code class="highlighter-rouge">2</code> par un <code class="highlighter-rouge">1</code> (rappelez-vous, les disques sont indexés du plus grand au plus petit). Le résultat attendu est donc <code class="highlighter-rouge">01100</code>.</p> <p>Notez que ce déplacement n’est pas toujours autorisé. Par exemple, le disque au sommet de la tour 2 est plus grand (i.e. a un plus petit index) que celui au sommet de la tour 0, il n’est donc pas possible de le déplacer vers la tour 0. C’est à l’algorithme que revient la responsabilité de n’effectuer que des déplacements <em>légaux</em>.</p> <p>Remplacer le dernier digit <em>x</em> de la représentation d’un nombre <em>N</em> (dans une base quelconque) par <em>y</em> peut s’implémenter récursivement :</p> <ul> <li>si le dernier digit <em>d</em> de <em>N</em> vaut <em>x</em>, le remplacer par <em>y</em> ;</li> <li>sinon, remplacer <em>x</em> par <em>y</em> dans <em>N</em> privé de son dernier digit, puis <em>recoller</em> le dernier digit à la fin.</li> </ul> <p>Concrètement :</p> <div class="highlighter-rouge"><pre class="codehilite"><code> N N\d d { x=2, y=1 } 01200 0120 0 // d != x : remplacer x par y dans N\d 0120 012 0 // d != x : remplacer x par y dans N\d 012 01 2 // d == x : remplacer x par y 011 01 1 // d = y 0110 011 0 // recoller d au résultat 01100 0110 0 // recoller d au résultat </code></pre></div> <p>Il reste à expliciter l’étape du remplacement du digit. De manière générale, remplacer par un chiffre <em>d</em> le dernier digit d’un nombre <em>N</em> exprimé dans une base <em>b</em> consiste à calculer, soit <code class="highlighter-rouge">N / b * b + d</code>, soit de manière équivalente <code class="highlighter-rouge">N - N % b + d</code> (<code class="highlighter-rouge">/</code> représentant la <a href="https://fr.wikipedia.org/wiki/Division_euclidienne">division entière</a> et <code class="highlighter-rouge">%</code> le <a href="https://fr.wikipedia.org/wiki/Modulo_%28op%C3%A9ration%29">modulo</a>). Dans les deux cas, l’opération annule le dernier digit puis ajoute son remplaçant.</p> <p class="center"><a href="http://cowbirdsinlove.com/43"><img src="/assets/metahanoi/base10.png" alt="base10" /></a></p> <p>Sur un exemple en base 10, c’est évident. Remplaçons le dernier chiffre de 125 par 7 selon les deux méthodes :</p> <div class="highlighter-rouge"><pre class="codehilite"><code> N / b * b + v N - N % b + d 125 / 10 * 10 + 7 125 - 125 % 10 + 7 12 * 10 + 7 125 - 5 + 7 120 + 7 120 + 7 127 127 </code></pre></div> <p>Arbitrairement, nous utiliserons la première méthode pour implémenter notre fonction <code class="highlighter-rouge">constexpr</code> <code class="highlighter-rouge">move(…)</code> :</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="k">constexpr</span> <span class="n">state</span> <span class="nf">move</span><span class="p">(</span><span class="n">state</span> <span class="n">s</span><span class="p">,</span> <span class="n">tower</span> <span class="n">src</span><span class="p">,</span> <span class="n">tower</span> <span class="n">target</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="n">s</span> <span class="o">%</span> <span class="mi">3</span> <span class="o">==</span> <span class="n">src</span> <span class="o">?</span> <span class="n">s</span> <span class="o">/</span> <span class="mi">3</span> <span class="o">*</span> <span class="mi">3</span> <span class="o">+</span> <span class="n">target</span> <span class="o">:</span> <span class="n">move</span><span class="p">(</span><span class="n">s</span> <span class="o">/</span> <span class="mi">3</span><span class="p">,</span> <span class="n">src</span><span class="p">,</span> <span class="n">target</span><span class="p">)</span> <span class="o">*</span> <span class="mi">3</span> <span class="o">+</span> <span class="n">s</span> <span class="o">%</span> <span class="mi">3</span><span class="p">;</span> <span class="p">}</span></code></pre></figure> <p>De la même manière, définissons une fonction <code class="highlighter-rouge">getTower(s, i)</code> qui retourne la tour sur laquelle se trouve le _i_ème plus petit disque :</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="k">constexpr</span> <span class="n">tower</span> <span class="nf">getTower</span><span class="p">(</span><span class="n">state</span> <span class="n">s</span><span class="p">,</span> <span class="n">size</span> <span class="n">disk</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="n">disk</span> <span class="o">==</span> <span class="mi">1</span> <span class="o">?</span> <span class="n">s</span> <span class="o">%</span> <span class="mi">3</span> <span class="o">:</span> <span class="n">getTower</span><span class="p">(</span><span class="n">s</span> <span class="o">/</span> <span class="mi">3</span><span class="p">,</span> <span class="n">disk</span> <span class="o">-</span> <span class="mi">1</span><span class="p">);</span> <span class="p">}</span></code></pre></figure> <p>Attaquons-nous maintenant à la conversion de la fonction <code class="highlighter-rouge">solveRec(…)</code>. Elle contenait deux branchements (<code class="highlighter-rouge">if</code>) et plusieurs instructions séquentielles, que nous allons devoir transformer en une seule instruction <code class="highlighter-rouge">return</code>.</p> <p>Pour cela, nous allons remplacer les branchements par des opérateurs ternaires :</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="k">if</span> <span class="p">(</span><span class="n">C</span><span class="p">)</span> <span class="p">{</span> <span class="n">X</span> <span class="o">=</span> <span class="n">A</span><span class="p">;</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="n">X</span> <span class="o">=</span> <span class="n">B</span><span class="p">;</span> <span class="p">}</span></code></pre></figure> <p>devient :</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="n">X</span> <span class="o">=</span> <span class="n">C</span> <span class="o">?</span> <span class="n">A</span> <span class="o">:</span> <span class="n">B</span><span class="p">;</span></code></pre></figure> <p>Notez que contrairement à la version <code class="highlighter-rouge">if</code>/<code class="highlighter-rouge">else</code>, cela implique que les expressions retournent une valeur. Cela tombe bien, comme une fonction <code class="highlighter-rouge">constexpr</code> ne peut pas modifier une variable, notre fonction va retourner l’état résultant de la transformation.</p> <p>Concernant les instructions séquentielles, remarquons qu’elles dépendent successivement les unes des autres. De manière générale, nous pouvons remplacer :</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="n">A</span> <span class="o">=</span> <span class="n">f</span><span class="p">();</span> <span class="n">B</span> <span class="o">=</span> <span class="n">g</span><span class="p">(</span><span class="n">A</span><span class="p">);</span> <span class="n">X</span> <span class="o">=</span> <span class="n">h</span><span class="p">(</span><span class="n">B</span><span class="p">);</span></code></pre></figure> <p>par:</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="n">X</span> <span class="o">=</span> <span class="n">h</span><span class="p">(</span><span class="n">g</span><span class="p">(</span><span class="n">f</span><span class="p">()));</span></code></pre></figure> <p>En combinant ces principes, la méthode <code class="highlighter-rouge">solve(…)</code> peut être écrite ainsi (afin de bien voir l’imbrication des appels, je ne la découpe volontairement pas en plusieurs méthodes) :</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="k">constexpr</span> <span class="n">state</span> <span class="nf">solve</span><span class="p">(</span><span class="n">size</span> <span class="n">disks</span><span class="p">,</span> <span class="n">state</span> <span class="n">s</span><span class="p">,</span> <span class="n">tower</span> <span class="n">target</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="n">disks</span> <span class="o">==</span> <span class="mi">0</span> <span class="o">?</span> <span class="n">s</span> <span class="o">:</span> <span class="n">getTower</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="n">disks</span><span class="p">)</span> <span class="o">==</span> <span class="n">target</span> <span class="o">?</span> <span class="n">solve</span><span class="p">(</span><span class="n">disks</span> <span class="o">-</span> <span class="mi">1</span><span class="p">,</span> <span class="n">s</span><span class="p">,</span> <span class="n">target</span><span class="p">)</span> <span class="o">:</span> <span class="n">solve</span><span class="p">(</span><span class="n">disks</span> <span class="o">-</span> <span class="mi">1</span><span class="p">,</span> <span class="n">move</span><span class="p">(</span><span class="n">solve</span><span class="p">(</span><span class="n">disks</span> <span class="o">-</span> <span class="mi">1</span><span class="p">,</span> <span class="n">s</span><span class="p">,</span> <span class="n">other</span><span class="p">(</span><span class="n">getTower</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="n">disks</span><span class="p">),</span> <span class="n">target</span><span class="p">)),</span> <span class="n">getTower</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="n">disks</span><span class="p">),</span> <span class="n">target</span><span class="p">),</span> <span class="n">target</span><span class="p">);</span> <span class="p">}</span></code></pre></figure> <p>Les appels les plus <em>profonds</em> sont effectués en premier.</p> <p>Ajoutons une méthode d’affichage pour obtenir la représentation de l’état du système en base 3 :</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="n">std</span><span class="o">::</span><span class="n">ostream</span> <span class="o">&amp;</span><span class="n">print_state</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">ostream</span> <span class="o">&amp;</span><span class="n">os</span><span class="p">,</span> <span class="n">size</span> <span class="n">ndisks</span><span class="p">,</span> <span class="n">state</span> <span class="n">s</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="n">ndisks</span> <span class="o">?</span> <span class="n">print_state</span><span class="p">(</span><span class="n">os</span><span class="p">,</span> <span class="n">ndisks</span> <span class="o">-</span> <span class="mi">1</span><span class="p">,</span> <span class="n">s</span> <span class="o">/</span> <span class="mi">3</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="n">s</span> <span class="o">%</span> <span class="mi">3</span> <span class="o">:</span> <span class="n">os</span><span class="p">;</span> <span class="p">}</span></code></pre></figure> <p>(<a href="https://github.com/rom1v/metahanoi/blob/constexpr/metahanoi.cpp">source</a> – <a href="https://github.com/rom1v/metahanoi/commit/constexpr">commit</a> – tag <code class="highlighter-rouge">constexpr</code>)</p> <p>Compilons et exécutons le programme :</p> <div class="highlighter-rouge"><pre class="codehilite"><code>$ g++ -std=c++11 metahanoi.cpp &amp;&amp; ./metahanoi 22222 </code></pre></div> <p>L’état <code class="highlighter-rouge">22222</code> (soit l’entier <em>242</em>) est bien écrit en dur dans le binaire généré :</p> <div class="highlighter-rouge"><pre class="codehilite"><code>$ g++ -std=c++11 -S metahanoi.cpp &amp;&amp; grep 242 metahanoi.s movq $242, -16(%rbp) movl $242, %edx </code></pre></div> <p>Le compilateur est donc bien parvenu à la solution.</p> <p>Mais avouons que le résultat est un peu décevant : l’état final, nous le connaissions déjà ; ce qui nous intéresse, c’est le cheminement pour y parvenir. Nous souhaiterions donc qu’en plus, le compilateur nous indique, d’une manière ou d’une autre, les étapes intermédiaires décrivant la solution du problème.</p> <h2 id="templates">Templates</h2> <p>Pour cela, nous allons utiliser les <a href="https://fr.wikipedia.org/wiki/Template_%28programmation%29">templates</a>.</p> <p>Pour comprendre comment les templates vont nous aider, commençons par quelques précisions.</p> <p>Les <a href="http://en.cppreference.com/w/cpp/language/class_template">classes template</a> sont souvent utilisées avec des paramètres <a href="http://en.cppreference.com/w/cpp/language/template_parameters#Template_type_arguments"><em>types</em></a>. Par exemple, <code class="highlighter-rouge">std::vector&lt;int&gt;</code> définit un nouveau type paramétré par le type <code class="highlighter-rouge">int</code>. Mais il est également possible de passer des paramètres <a href="http://en.cppreference.com/w/cpp/language/template_parameters#Template_non-type_arguments"><em>non-types</em></a>, qui sont alors des valeurs “simples”.</p> <p>Une <em>classe template</em> ne définit pas un type, mais permet de générer des types selon les paramètres qui lui sont affectés. Concrètement, <code class="highlighter-rouge">std::vector&lt;int&gt;</code> et <code class="highlighter-rouge">std::vector&lt;double&gt;</code> sont des types totalement différents, comme s’ils étaient implémentés par deux classes écrites séparément.</p> <p>Dit autrement, <strong>la classe est au template ce que l’objet est à la classe : une instance</strong>. Mais c’est une <em>instance</em> qui existe lors de la phase de compilation !</p> <div class="highlighter-rouge"><pre class="codehilite"><code> +----------------+ | CLASS TEMPLATE | +----------------+ | template | instantiation v COMPILE TIME +----------------+ | CLASS / TYPE | ----------------------- +----------------+ | class RUNTIME | instantiation v +----------------+ | OBJECT | +----------------+ </code></pre></div> <p>De la même manière qu’une variable d’instance existe pour chaque objet (instance de classe), une variable <code class="highlighter-rouge">static</code> existe pour chaque classe (instance de template).</p> <p>Pour conserver les états successifs de la résolution du problème des tours de Hanoï, nous allons donc créer une classe par étape, dans laquelle nous allons pouvoir y stocker un état <code class="highlighter-rouge">static</code>. Nous voulons donc remplacer notre fonction <code class="highlighter-rouge">solve(…)</code> par des <em>classes template</em>.</p> <p>Pour illustrer comment, commençons par un template simple effectuant la somme de deux entiers :</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="k">template</span> <span class="o">&lt;</span><span class="kt">int</span> <span class="n">I</span><span class="p">,</span> <span class="kt">int</span> <span class="n">J</span><span class="o">&gt;</span> <span class="k">struct</span> <span class="n">Sum</span> <span class="p">{</span> <span class="k">static</span> <span class="k">constexpr</span> <span class="kt">int</span> <span class="n">result</span> <span class="o">=</span> <span class="n">I</span> <span class="o">+</span> <span class="n">J</span><span class="p">;</span> <span class="p">};</span></code></pre></figure> <p>Ainsi, l’expression :</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="n">Sum</span><span class="o">&lt;</span><span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="o">&gt;::</span><span class="n">result</span></code></pre></figure> <p>est calculée à la compilation et vaut 7.</p> <p>Prenons maintenant l’exemple d’un calcul récursif : la <a href="https://fr.wikipedia.org/wiki/Factorielle">factorielle</a> d’un entier. Il nous faut implémenter à la fois le cas général et la condition d’arrêt. Nous pourrions penser à utiliser l’opérateur ternaire ainsi :</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="k">template</span> <span class="o">&lt;</span><span class="kt">int</span> <span class="n">N</span><span class="o">&gt;</span> <span class="k">struct</span> <span class="n">Fact</span> <span class="p">{</span> <span class="c1">// does not work! </span> <span class="k">static</span> <span class="k">constexpr</span> <span class="kt">int</span> <span class="n">result</span> <span class="o">=</span> <span class="n">N</span> <span class="o">==</span> <span class="mi">0</span> <span class="o">?</span> <span class="mi">1</span> <span class="o">:</span> <span class="n">N</span> <span class="o">*</span> <span class="n">Fact</span><span class="o">&lt;</span><span class="n">N</span> <span class="o">-</span> <span class="mi">1</span><span class="o">&gt;::</span><span class="n">result</span><span class="p">;</span> <span class="p">};</span></code></pre></figure> <p>Malheureusement, ceci ne peut pas fonctionner, car pour calculer la valeur d’une expression, le compilateur doit d’abord calculer le type de chacun des opérandes. Par conséquent, <code class="highlighter-rouge">Fact&lt;N - 1&gt;</code> sera généré même si <code class="highlighter-rouge">N == 0</code>. La récursivité ne s’arrête donc jamais, provoquant une erreur à la compilation :</p> <div class="highlighter-rouge"><pre class="codehilite"><code>error: template instantiation depth exceeds maximum of 900 </code></pre></div> <p>Comment faire alors ? La clé réside dans la <a href="http://www.cprogramming.com/tutorial/template_specialization.html">spécialisation de templates</a>, qui permet de sélectionner l’implémentation de la classe en fonction des paramètres :</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="k">template</span> <span class="o">&lt;</span><span class="kt">int</span> <span class="n">N</span><span class="o">&gt;</span> <span class="k">struct</span> <span class="n">Fact</span> <span class="p">{</span> <span class="k">static</span> <span class="k">constexpr</span> <span class="kt">int</span> <span class="n">result</span> <span class="o">=</span> <span class="n">N</span> <span class="o">*</span> <span class="n">Fact</span><span class="o">&lt;</span><span class="n">N</span><span class="o">-</span><span class="mi">1</span><span class="o">&gt;::</span><span class="n">result</span><span class="p">;</span> <span class="p">};</span> <span class="k">template</span> <span class="o">&lt;&gt;</span> <span class="k">struct</span> <span class="n">Fact</span><span class="o">&lt;</span><span class="mi">0</span><span class="o">&gt;</span> <span class="p">{</span> <span class="k">static</span> <span class="k">constexpr</span> <span class="kt">int</span> <span class="n">result</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="p">};</span></code></pre></figure> <p>Lorsque <code class="highlighter-rouge">Fact</code> est instancié avec le paramètre <code class="highlighter-rouge">0</code>, la classe est générée à partir du template spécialisé, stoppant ainsi la récursivité.</p> <p>Appliquons ce principe à notre algorithme des tours de Hanoï. Nous allons définir une classe template <code class="highlighter-rouge">Solver</code> avec 3 paramètres de template, les mêmes que notre fonction <code class="highlighter-rouge">solve(…)</code> :</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="k">template</span> <span class="o">&lt;</span><span class="n">size</span> <span class="n">DISKS</span><span class="p">,</span> <span class="n">state</span> <span class="n">S</span><span class="p">,</span> <span class="n">tower</span> <span class="n">TARGET</span><span class="o">&gt;</span> <span class="k">struct</span> <span class="n">SolverRec</span> <span class="p">{</span> <span class="cm">/* … */</span> <span class="p">};</span></code></pre></figure> <p>Puis nous allons en définir une spécialisation pour le cas où <code class="highlighter-rouge">DISKS</code> vaut 0 :</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="k">template</span> <span class="o">&lt;</span><span class="n">state</span> <span class="n">S</span><span class="p">,</span> <span class="n">tower</span> <span class="n">TARGET</span><span class="o">&gt;</span> <span class="k">struct</span> <span class="n">SolverRec</span><span class="o">&lt;</span><span class="mi">0</span><span class="p">,</span> <span class="n">S</span><span class="p">,</span> <span class="n">TARGET</span><span class="o">&gt;</span> <span class="p">{</span> <span class="cm">/* … */</span> <span class="p">};</span></code></pre></figure> <p>Nous avons ainsi implémenté le premier branchement sur la condition <code class="highlighter-rouge">DISKS == 0</code>.</p> <p>Un second branchement reste à réaliser : le calcul à effectuer est différent selon si le plus grand disque parmi les <code class="highlighter-rouge">DISKS</code> derniers est déjà sur la tour cible ou non. Celui-ci est plus compliqué, car les paramètres de template présents ne permettent pas d’évaluer sa condition.</p> <p>Nous allons donc devoir ajouter en paramètre la position du disque <code class="highlighter-rouge">SRC</code> afin de pouvoir sélectionner la bonne implémentation en fonction de la condition <code class="highlighter-rouge">SRC == TARGET</code>. Sa valeur étant déterminée par celle des autres paramètres, l’ajout de <code class="highlighter-rouge">SRC</code> ne va pas entraîner la création de nouveaux types. Par contre, il multiplie les cas à implémenter :</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="c1">// cas général </span><span class="k">template</span> <span class="o">&lt;</span><span class="n">size</span> <span class="n">DISKS</span><span class="p">,</span> <span class="n">state</span> <span class="n">S</span><span class="p">,</span> <span class="n">tower</span> <span class="n">SRC</span><span class="p">,</span> <span class="n">tower</span> <span class="n">TARGET</span><span class="o">&gt;</span> <span class="k">struct</span> <span class="n">SolverRec</span> <span class="p">{</span> <span class="cm">/* … */</span> <span class="p">};</span> <span class="c1">// quand SRC == TARGET (le disque est déjà bien placé) </span><span class="k">template</span> <span class="o">&lt;</span><span class="n">size</span> <span class="n">DISKS</span><span class="p">,</span> <span class="n">state</span> <span class="n">S</span><span class="p">,</span> <span class="n">tower</span> <span class="n">TOWER</span><span class="o">&gt;</span> <span class="k">struct</span> <span class="n">SolverRec</span><span class="o">&lt;</span><span class="n">DISKS</span><span class="p">,</span> <span class="n">S</span><span class="p">,</span> <span class="n">TOWER</span><span class="p">,</span> <span class="n">TOWER</span><span class="o">&gt;</span> <span class="p">{</span> <span class="cm">/* … */</span> <span class="p">};</span> <span class="c1">// quand il ne reste plus qu'un seul disque, mal placé </span><span class="k">template</span> <span class="o">&lt;</span><span class="n">state</span> <span class="n">S</span><span class="p">,</span> <span class="n">tower</span> <span class="n">SRC</span><span class="p">,</span> <span class="n">tower</span> <span class="n">TARGET</span><span class="o">&gt;</span> <span class="k">struct</span> <span class="n">SolverRec</span><span class="o">&lt;</span><span class="mi">1</span><span class="p">,</span> <span class="n">S</span><span class="p">,</span> <span class="n">SRC</span><span class="p">,</span> <span class="n">TARGET</span><span class="o">&gt;</span> <span class="p">{</span> <span class="cm">/* … */</span> <span class="p">};</span> <span class="c1">// quand il ne reste plus qu'un seul disque, déjà bien placé </span><span class="k">template</span> <span class="o">&lt;</span><span class="n">state</span> <span class="n">S</span><span class="p">,</span> <span class="n">tower</span> <span class="n">TOWER</span><span class="o">&gt;</span> <span class="k">struct</span> <span class="n">SolverRec</span><span class="o">&lt;</span><span class="mi">1</span><span class="p">,</span> <span class="n">S</span><span class="p">,</span> <span class="n">TOWER</span><span class="p">,</span> <span class="n">TOWER</span><span class="o">&gt;</span> <span class="p">{</span> <span class="cm">/* … */</span> <span class="p">};</span></code></pre></figure> <p>Les plus observateurs auront remarqué que désormais, la récursivité s’arrête à 1 disque, et non plus 0 comme précédemment. En effet, maintenant que le paramètre <code class="highlighter-rouge">SRC</code> est ajouté, il va falloir le calculer (grâce à <code class="highlighter-rouge">getTower(…)</code>) avant d’utiliser le type. Or, cela n’a pas de sens de récupérer la position d’un disque lorsque nous n’avons pas de disques. D’ailleurs, l’exécution de <code class="highlighter-rouge">getTower(…)</code> avec <code class="highlighter-rouge">disk == 0</code> provoquerait une erreur… de compilation (vu que l’exécution se déroule à la compilation).</p> <p>Nous avons maintenant 4 versions de la classe template <code class="highlighter-rouge">SolverRec</code> à écrire. Chacune devra contenir l’état final résultant du déplacement de <code class="highlighter-rouge">DISKS</code> disques de la tour <code class="highlighter-rouge">SRC</code> vers la tour <code class="highlighter-rouge">TARGET</code> à partir de l’état <code class="highlighter-rouge">S</code>. Cet état sera stocké dans une constante <code class="highlighter-rouge">final_state</code>, présente dans chacune des spécialisations.</p> <p>Voici mon implémentation :</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="k">template</span> <span class="o">&lt;</span><span class="n">size</span> <span class="n">DISKS</span><span class="p">,</span> <span class="n">state</span> <span class="n">S</span><span class="p">,</span> <span class="n">tower</span> <span class="n">SRC</span><span class="p">,</span> <span class="n">tower</span> <span class="n">TARGET</span><span class="o">&gt;</span> <span class="k">struct</span> <span class="n">SolverRec</span> <span class="p">{</span> <span class="k">static</span> <span class="k">constexpr</span> <span class="n">tower</span> <span class="n">nextSrc</span> <span class="o">=</span> <span class="n">getTower</span><span class="p">(</span><span class="n">S</span><span class="p">,</span> <span class="n">DISKS</span> <span class="o">-</span> <span class="mi">1</span><span class="p">);</span> <span class="k">static</span> <span class="k">constexpr</span> <span class="n">tower</span> <span class="n">inter</span> <span class="o">=</span> <span class="n">other</span><span class="p">(</span><span class="n">SRC</span><span class="p">,</span> <span class="n">TARGET</span><span class="p">);</span> <span class="k">using</span> <span class="n">rec1</span> <span class="o">=</span> <span class="n">SolverRec</span><span class="o">&lt;</span><span class="n">DISKS</span> <span class="o">-</span> <span class="mi">1</span><span class="p">,</span> <span class="n">S</span><span class="p">,</span> <span class="n">nextSrc</span><span class="p">,</span> <span class="n">inter</span><span class="o">&gt;</span><span class="p">;</span> <span class="k">static</span> <span class="k">constexpr</span> <span class="n">state</span> <span class="n">value</span> <span class="o">=</span> <span class="n">move</span><span class="p">(</span><span class="n">rec1</span><span class="o">::</span><span class="n">final_state</span><span class="p">,</span> <span class="n">SRC</span><span class="p">,</span> <span class="n">TARGET</span><span class="p">);</span> <span class="k">using</span> <span class="n">rec2</span> <span class="o">=</span> <span class="n">SolverRec</span><span class="o">&lt;</span><span class="n">DISKS</span> <span class="o">-</span> <span class="mi">1</span><span class="p">,</span> <span class="n">value</span><span class="p">,</span> <span class="n">inter</span><span class="p">,</span> <span class="n">TARGET</span><span class="o">&gt;</span><span class="p">;</span> <span class="k">static</span> <span class="k">constexpr</span> <span class="n">state</span> <span class="n">final_state</span> <span class="o">=</span> <span class="n">rec2</span><span class="o">::</span><span class="n">final_state</span><span class="p">;</span> <span class="p">};</span> <span class="k">template</span> <span class="o">&lt;</span><span class="n">size</span> <span class="n">DISKS</span><span class="p">,</span> <span class="n">state</span> <span class="n">S</span><span class="p">,</span> <span class="n">tower</span> <span class="n">TOWER</span><span class="o">&gt;</span> <span class="k">struct</span> <span class="n">SolverRec</span><span class="o">&lt;</span><span class="n">DISKS</span><span class="p">,</span> <span class="n">S</span><span class="p">,</span> <span class="n">TOWER</span><span class="p">,</span> <span class="n">TOWER</span><span class="o">&gt;</span> <span class="p">{</span> <span class="k">static</span> <span class="k">constexpr</span> <span class="n">tower</span> <span class="n">nextSrc</span> <span class="o">=</span> <span class="n">getTower</span><span class="p">(</span><span class="n">S</span><span class="p">,</span> <span class="n">DISKS</span> <span class="o">-</span> <span class="mi">1</span><span class="p">);</span> <span class="k">using</span> <span class="n">rec</span> <span class="o">=</span> <span class="n">SolverRec</span><span class="o">&lt;</span><span class="n">DISKS</span> <span class="o">-</span> <span class="mi">1</span><span class="p">,</span> <span class="n">S</span><span class="p">,</span> <span class="n">nextSrc</span><span class="p">,</span> <span class="n">TOWER</span><span class="o">&gt;</span><span class="p">;</span> <span class="k">static</span> <span class="k">constexpr</span> <span class="n">state</span> <span class="n">final_state</span> <span class="o">=</span> <span class="n">rec</span><span class="o">::</span><span class="n">final_state</span><span class="p">;</span> <span class="p">};</span> <span class="k">template</span> <span class="o">&lt;</span><span class="n">state</span> <span class="n">S</span><span class="p">,</span> <span class="n">tower</span> <span class="n">SRC</span><span class="p">,</span> <span class="n">tower</span> <span class="n">TARGET</span><span class="o">&gt;</span> <span class="k">struct</span> <span class="n">SolverRec</span><span class="o">&lt;</span><span class="mi">1</span><span class="p">,</span> <span class="n">S</span><span class="p">,</span> <span class="n">SRC</span><span class="p">,</span> <span class="n">TARGET</span><span class="o">&gt;</span> <span class="p">{</span> <span class="k">static</span> <span class="k">constexpr</span> <span class="n">state</span> <span class="n">final_state</span> <span class="o">=</span> <span class="n">move</span><span class="p">(</span><span class="n">S</span><span class="p">,</span> <span class="n">SRC</span><span class="p">,</span> <span class="n">TARGET</span><span class="p">);</span> <span class="p">};</span> <span class="k">template</span> <span class="o">&lt;</span><span class="n">state</span> <span class="n">S</span><span class="p">,</span> <span class="n">tower</span> <span class="n">TOWER</span><span class="o">&gt;</span> <span class="k">struct</span> <span class="n">SolverRec</span><span class="o">&lt;</span><span class="mi">1</span><span class="p">,</span> <span class="n">S</span><span class="p">,</span> <span class="n">TOWER</span><span class="p">,</span> <span class="n">TOWER</span><span class="o">&gt;</span> <span class="p">{</span> <span class="k">static</span> <span class="k">constexpr</span> <span class="n">state</span> <span class="n">final_state</span> <span class="o">=</span> <span class="n">S</span><span class="p">;</span> <span class="p">};</span></code></pre></figure> <p>Le type (déterminé par les arguments des templates) correspondant aux appels récursifs dépend des valeurs <code class="highlighter-rouge">constexpr</code> calculées dans la classe. C’est à l’appelant de calculer la tour source pour renseigner la valeur du paramètre <code class="highlighter-rouge">SRC</code>.</p> <p>Par commodité, nous pouvons aussi ajouter une <em>classe template</em> <code class="highlighter-rouge">Solver</code>, qui calcule elle-même la tour <code class="highlighter-rouge">SRC</code> du plus grand disque lors du démarrage.</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="k">template</span> <span class="o">&lt;</span><span class="n">size</span> <span class="n">DISKS</span><span class="p">,</span> <span class="n">state</span> <span class="n">S</span><span class="p">,</span> <span class="n">tower</span> <span class="n">TARGET</span><span class="o">&gt;</span> <span class="k">struct</span> <span class="n">Solver</span> <span class="p">{</span> <span class="k">static</span> <span class="k">constexpr</span> <span class="n">tower</span> <span class="n">src</span> <span class="o">=</span> <span class="n">getTower</span><span class="p">(</span><span class="n">S</span><span class="p">,</span> <span class="n">DISKS</span><span class="p">);</span> <span class="k">using</span> <span class="n">start</span> <span class="o">=</span> <span class="n">SolverRec</span><span class="o">&lt;</span><span class="n">DISKS</span><span class="p">,</span> <span class="n">S</span><span class="p">,</span> <span class="n">src</span><span class="p">,</span> <span class="n">TARGET</span><span class="o">&gt;</span><span class="p">;</span> <span class="k">static</span> <span class="k">constexpr</span> <span class="n">state</span> <span class="n">final_state</span> <span class="o">=</span> <span class="n">start</span><span class="o">::</span><span class="n">final_state</span><span class="p">;</span> <span class="p">};</span></code></pre></figure> <p>(<a href="https://github.com/rom1v/metahanoi/blob/templates/metahanoi.cpp">source</a> – <a href="https://github.com/rom1v/metahanoi/commit/templates">commit</a> – tag <code class="highlighter-rouge">templates</code>)</p> <p>De cette manière, pour calculer l’état résultant du déplacement de 5 disques à l’état <code class="highlighter-rouge">00000</code> (l’entier 0) vers la tour 2, il suffit de lire la variable :</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="n">Solver</span><span class="o">&lt;</span><span class="mi">5</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">2</span><span class="o">&gt;::</span><span class="n">final_state</span></code></pre></figure> <p>Nous avons donc converti notre implementation d’une simple fonction <code class="highlighter-rouge">constexpr</code> en <em>classes template</em>. Fonctionnellement équivalente pour l’instant, cette nouvelle version met en place les fondations pour récupérer, à l’exécution, les étapes de la résolution du problème calculées à la compilation.</p> <h2 id="tat-initial">État initial</h2> <p>Mais avant cela, dotons-nous d’un outil pour décrire l’état initial facilement, qui pour l’instant doit être exprimé grâce à un <code class="highlighter-rouge">state</code>, c’est-à-dire un entier.</p> <p>L’idée serait que l’état et le nombre de disques soit calculé automatiquement à la compilation à partir de la liste des positions des disques :</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="n">Disks</span><span class="o">&lt;</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="o">&gt;</span></code></pre></figure> <p>Contrairement à précedemment, ici, nous avons besoin d’un nombre indéterminé de paramètres. Nous allons donc utiliser les <a href="https://en.wikipedia.org/wiki/Variadic_template">variadic templates</a> :</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="k">template</span> <span class="o">&lt;</span><span class="n">tower</span> <span class="p">...</span><span class="n">T</span><span class="o">&gt;</span> <span class="k">struct</span> <span class="n">Disks</span><span class="p">;</span></code></pre></figure> <p>Remarquez que ce template est juste <em>déclaré</em> et non <em>défini</em>.</p> <p>Pour parcourir les paramètres, nous avons besoin de deux spécialisations (une pour la récursion et une pour la condition d’arrêt) :</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="k">template</span> <span class="o">&lt;</span><span class="n">tower</span> <span class="n">H</span><span class="p">,</span> <span class="n">tower</span> <span class="p">...</span><span class="n">T</span><span class="o">&gt;</span> <span class="k">struct</span> <span class="n">Disks</span><span class="o">&lt;</span><span class="n">H</span><span class="p">,</span> <span class="n">T</span><span class="p">...</span><span class="o">&gt;</span> <span class="p">{</span> <span class="cm">/* … */</span> <span class="p">};</span> <span class="k">template</span> <span class="o">&lt;&gt;</span> <span class="k">struct</span> <span class="n">Disks</span><span class="o">&lt;&gt;</span> <span class="p">{</span> <span class="cm">/* … */</span> <span class="p">};</span></code></pre></figure> <p>Chacune des deux spécialisent le template déclaré, mais remarquez que l’une n’est pas une spécialisation de l’autre. C’est la raison pour laquelle nous avons besoin de déclarer un template (sans le définir) dont ces deux définitions sont une spécialisation.</p> <p>Voici l’implémentation :</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="k">template</span> <span class="o">&lt;</span><span class="n">tower</span> <span class="n">H</span><span class="p">,</span> <span class="n">tower</span> <span class="p">...</span><span class="n">T</span><span class="o">&gt;</span> <span class="k">struct</span> <span class="n">Disks</span><span class="o">&lt;</span><span class="n">H</span><span class="p">,</span> <span class="n">T</span><span class="p">...</span><span class="o">&gt;</span> <span class="p">{</span> <span class="k">static</span> <span class="k">constexpr</span> <span class="n">state</span> <span class="n">count</span> <span class="o">=</span> <span class="mi">1</span> <span class="o">+</span> <span class="k">sizeof</span><span class="p">...(</span><span class="n">T</span><span class="p">);</span> <span class="k">static</span> <span class="k">constexpr</span> <span class="n">state</span> <span class="nf">pack</span><span class="p">(</span><span class="n">state</span> <span class="n">tmp</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="n">Disks</span><span class="o">&lt;</span><span class="n">T</span><span class="p">...</span><span class="o">&gt;::</span><span class="n">pack</span><span class="p">(</span><span class="n">tmp</span> <span class="o">*</span> <span class="mi">3</span> <span class="o">+</span> <span class="n">H</span><span class="p">);</span> <span class="p">}</span> <span class="k">static</span> <span class="k">constexpr</span> <span class="n">state</span> <span class="n">packed</span> <span class="o">=</span> <span class="n">pack</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span> <span class="p">};</span> <span class="k">template</span> <span class="o">&lt;&gt;</span> <span class="k">struct</span> <span class="n">Disks</span><span class="o">&lt;&gt;</span> <span class="p">{</span> <span class="k">static</span> <span class="k">constexpr</span> <span class="n">state</span> <span class="n">count</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="k">static</span> <span class="k">constexpr</span> <span class="n">state</span> <span class="nf">pack</span><span class="p">(</span><span class="n">state</span> <span class="n">tmp</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="n">tmp</span><span class="p">;</span> <span class="p">};</span> <span class="k">static</span> <span class="k">constexpr</span> <span class="n">state</span> <span class="n">packed</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="p">};</span></code></pre></figure> <p>Le nombre de disques est initialisé en comptant les paramètres grâce à l’opérateur <a href="http://en.cppreference.com/w/cpp/language/sizeof..."><code class="highlighter-rouge">sizeof...</code></a>.</p> <p>L’état compacté est stocké dans la variable <code class="highlighter-rouge">packed</code>. Étant donné que les premiers paramètres traités seront les digits <em>de poids fort</em>, la multiplication devra être effectuée par les appels récursifs plus profonds. C’est la raison pour laquelle j’utilise une fonction temporaire qui permet d’initialiser <code class="highlighter-rouge">packed</code>.</p> <p>Nous pouvons maintenant initialiser notre <code class="highlighter-rouge">solver</code> ainsi :</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="k">using</span> <span class="n">disks</span> <span class="o">=</span> <span class="n">Disks</span><span class="o">&lt;</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="o">&gt;</span><span class="p">;</span> <span class="k">using</span> <span class="n">solver</span> <span class="o">=</span> <span class="n">Solver</span><span class="o">&lt;</span><span class="n">disks</span><span class="o">::</span><span class="n">count</span><span class="p">,</span> <span class="n">disks</span><span class="o">::</span><span class="n">packed</span><span class="p">,</span> <span class="mi">2</span><span class="o">&gt;</span><span class="p">;</span></code></pre></figure> <p>(<a href="https://github.com/rom1v/metahanoi/blob/disks/metahanoi.cpp">source</a> – <a href="https://github.com/rom1v/metahanoi/commit/disks">commit</a> – tag <code class="highlighter-rouge">disks</code>)</p> <h2 id="affichage-rcursif">Affichage récursif</h2> <p>Attaquons-nous maintenant à l’affichage des états successifs.</p> <p>Le plus simple consiste à implémenter une méthode <code class="highlighter-rouge">print(…)</code> dans chacune des classes <code class="highlighter-rouge">SolverRec</code>, affichant l’état associé et/ou appellant récursivement les méthodes <code class="highlighter-rouge">print(…)</code> sur les instances de <code class="highlighter-rouge">SolverRec</code> utilisées pour la résolution du problème.</p> <p>Pour cela, nous devons auparavant déterminer, pour chaque template instancié, quel état afficher. Par exemple, pour les classes crées à partir de l’implémentation du template non spécialisé, il y a plusieurs états accessibles :</p> <ul> <li>l’état initial (<code class="highlighter-rouge">S</code>) ;</li> <li>l’état après le premier appel récursif (<code class="highlighter-rouge">rec1::final_state</code>) ;</li> <li>l’état après le déplacement (<code class="highlighter-rouge">value</code>) ;</li> <li>l’état après le second appel récursif (<code class="highlighter-rouge">final_state</code>).</li> </ul> <p>C’est en fait l’état <code class="highlighter-rouge">value</code> qu’il faut afficher :</p> <ul> <li>c’est le seul endroit où le déplacement du disque entre les deux appels récursif est connu ;</li> <li>les états correspondant aux appels récursifs seront gérés par leurs classes correspondantes.</li> </ul> <p>Il est important de différencier, pour chaque <code class="highlighter-rouge">SolverRec</code>, l’état final, représentant l’état après l’application de tous les déplacements demandés, de l’état suivant le seul déplacement unitaire (s’il existe) associé à la classe. C’est ce dernier que nous voulons afficher.</p> <p>Nous allons donc ajouter dans les 4 versions du template <code class="highlighter-rouge">SolverRec</code> une méthode :</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="k">static</span> <span class="n">std</span><span class="o">::</span><span class="n">ostream</span> <span class="o">&amp;</span><span class="n">print</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">ostream</span> <span class="o">&amp;</span><span class="n">os</span><span class="p">,</span> <span class="n">size</span> <span class="n">ndisks</span><span class="p">);</span></code></pre></figure> <p>Le paramètre <code class="highlighter-rouge">std::ostream &amp;os</code> permet juste de préciser sur quel flux écrire (<code class="highlighter-rouge">std::cout</code> par exemple) ; il est retourné pour pouvoir le chaîner avec d’autres écritures (comme <code class="highlighter-rouge">&lt;&lt; std::endl</code>).</p> <p>Cette méthode a besoin de connaître le nombre total de disques, afin d’afficher le bon nombre de digits. Notez que cette valeur est différente du paramètre de template <code class="highlighter-rouge">DISKS</code>, qui correspond au nombre de disques à déplacer pour le niveau de récursivité courant.</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="k">template</span> <span class="o">&lt;</span><span class="n">size</span> <span class="n">DISKS</span><span class="p">,</span> <span class="n">state</span> <span class="n">S</span><span class="p">,</span> <span class="n">tower</span> <span class="n">SRC</span><span class="p">,</span> <span class="n">tower</span> <span class="n">TARGET</span><span class="o">&gt;</span> <span class="k">struct</span> <span class="n">SolverRec</span> <span class="p">{</span> <span class="k">static</span> <span class="k">constexpr</span> <span class="n">tower</span> <span class="n">nextSrc</span> <span class="o">=</span> <span class="n">getTower</span><span class="p">(</span><span class="n">S</span><span class="p">,</span> <span class="n">DISKS</span> <span class="o">-</span> <span class="mi">1</span><span class="p">);</span> <span class="k">static</span> <span class="k">constexpr</span> <span class="n">tower</span> <span class="n">inter</span> <span class="o">=</span> <span class="n">other</span><span class="p">(</span><span class="n">SRC</span><span class="p">,</span> <span class="n">TARGET</span><span class="p">);</span> <span class="k">using</span> <span class="n">rec1</span> <span class="o">=</span> <span class="n">SolverRec</span><span class="o">&lt;</span><span class="n">DISKS</span> <span class="o">-</span> <span class="mi">1</span><span class="p">,</span> <span class="n">S</span><span class="p">,</span> <span class="n">nextSrc</span><span class="p">,</span> <span class="n">inter</span><span class="o">&gt;</span><span class="p">;</span> <span class="k">static</span> <span class="k">constexpr</span> <span class="n">state</span> <span class="n">value</span> <span class="o">=</span> <span class="n">move</span><span class="p">(</span><span class="n">rec1</span><span class="o">::</span><span class="n">final_state</span><span class="p">,</span> <span class="n">SRC</span><span class="p">,</span> <span class="n">TARGET</span><span class="p">);</span> <span class="k">using</span> <span class="n">rec2</span> <span class="o">=</span> <span class="n">SolverRec</span><span class="o">&lt;</span><span class="n">DISKS</span> <span class="o">-</span> <span class="mi">1</span><span class="p">,</span> <span class="n">value</span><span class="p">,</span> <span class="n">inter</span><span class="p">,</span> <span class="n">TARGET</span><span class="o">&gt;</span><span class="p">;</span> <span class="k">static</span> <span class="k">constexpr</span> <span class="n">state</span> <span class="n">final_state</span> <span class="o">=</span> <span class="n">rec2</span><span class="o">::</span><span class="n">final_state</span><span class="p">;</span> <span class="k">static</span> <span class="n">std</span><span class="o">::</span><span class="n">ostream</span> <span class="o">&amp;</span><span class="n">print</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">ostream</span> <span class="o">&amp;</span><span class="n">os</span><span class="p">,</span> <span class="n">size</span> <span class="n">ndisks</span><span class="p">)</span> <span class="p">{</span> <span class="n">rec1</span><span class="o">::</span><span class="n">print</span><span class="p">(</span><span class="n">os</span><span class="p">,</span> <span class="n">ndisks</span><span class="p">);</span> <span class="n">print_state</span><span class="p">(</span><span class="n">os</span><span class="p">,</span> <span class="n">ndisks</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span> <span class="n">rec2</span><span class="o">::</span><span class="n">print</span><span class="p">(</span><span class="n">os</span><span class="p">,</span> <span class="n">ndisks</span><span class="p">);</span> <span class="k">return</span> <span class="n">os</span><span class="p">;</span> <span class="p">}</span> <span class="p">};</span> <span class="k">template</span> <span class="o">&lt;</span><span class="n">size</span> <span class="n">DISKS</span><span class="p">,</span> <span class="n">state</span> <span class="n">S</span><span class="p">,</span> <span class="n">tower</span> <span class="n">TOWER</span><span class="o">&gt;</span> <span class="k">struct</span> <span class="n">SolverRec</span><span class="o">&lt;</span><span class="n">DISKS</span><span class="p">,</span> <span class="n">S</span><span class="p">,</span> <span class="n">TOWER</span><span class="p">,</span> <span class="n">TOWER</span><span class="o">&gt;</span> <span class="p">{</span> <span class="k">static</span> <span class="k">constexpr</span> <span class="n">tower</span> <span class="n">nextSrc</span> <span class="o">=</span> <span class="n">getTower</span><span class="p">(</span><span class="n">S</span><span class="p">,</span> <span class="n">DISKS</span> <span class="o">-</span> <span class="mi">1</span><span class="p">);</span> <span class="k">using</span> <span class="n">rec</span> <span class="o">=</span> <span class="n">SolverRec</span><span class="o">&lt;</span><span class="n">DISKS</span> <span class="o">-</span> <span class="mi">1</span><span class="p">,</span> <span class="n">S</span><span class="p">,</span> <span class="n">nextSrc</span><span class="p">,</span> <span class="n">TOWER</span><span class="o">&gt;</span><span class="p">;</span> <span class="k">static</span> <span class="k">constexpr</span> <span class="n">state</span> <span class="n">final_state</span> <span class="o">=</span> <span class="n">rec</span><span class="o">::</span><span class="n">final_state</span><span class="p">;</span> <span class="k">static</span> <span class="n">std</span><span class="o">::</span><span class="n">ostream</span> <span class="o">&amp;</span><span class="n">print</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">ostream</span> <span class="o">&amp;</span><span class="n">os</span><span class="p">,</span> <span class="n">size</span> <span class="n">ndisks</span><span class="p">)</span> <span class="p">{</span> <span class="n">rec</span><span class="o">::</span><span class="n">print</span><span class="p">(</span><span class="n">os</span><span class="p">,</span> <span class="n">ndisks</span><span class="p">);</span> <span class="k">return</span> <span class="n">os</span><span class="p">;</span> <span class="p">}</span> <span class="p">};</span> <span class="k">template</span> <span class="o">&lt;</span><span class="n">state</span> <span class="n">S</span><span class="p">,</span> <span class="n">tower</span> <span class="n">SRC</span><span class="p">,</span> <span class="n">tower</span> <span class="n">TARGET</span><span class="o">&gt;</span> <span class="k">struct</span> <span class="n">SolverRec</span><span class="o">&lt;</span><span class="mi">1</span><span class="p">,</span> <span class="n">S</span><span class="p">,</span> <span class="n">SRC</span><span class="p">,</span> <span class="n">TARGET</span><span class="o">&gt;</span> <span class="p">{</span> <span class="k">static</span> <span class="k">constexpr</span> <span class="n">state</span> <span class="n">value</span> <span class="o">=</span> <span class="n">move</span><span class="p">(</span><span class="n">S</span><span class="p">,</span> <span class="n">SRC</span><span class="p">,</span> <span class="n">TARGET</span><span class="p">);</span> <span class="k">static</span> <span class="k">constexpr</span> <span class="n">state</span> <span class="n">final_state</span> <span class="o">=</span> <span class="n">value</span><span class="p">;</span> <span class="k">static</span> <span class="n">std</span><span class="o">::</span><span class="n">ostream</span> <span class="o">&amp;</span><span class="n">print</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">ostream</span> <span class="o">&amp;</span><span class="n">os</span><span class="p">,</span> <span class="n">size</span> <span class="n">ndisks</span><span class="p">)</span> <span class="p">{</span> <span class="n">print_state</span><span class="p">(</span><span class="n">os</span><span class="p">,</span> <span class="n">ndisks</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span> <span class="k">return</span> <span class="n">os</span><span class="p">;</span> <span class="p">}</span> <span class="p">};</span> <span class="k">template</span> <span class="o">&lt;</span><span class="n">state</span> <span class="n">S</span><span class="p">,</span> <span class="n">tower</span> <span class="n">TOWER</span><span class="o">&gt;</span> <span class="k">struct</span> <span class="n">SolverRec</span><span class="o">&lt;</span><span class="mi">1</span><span class="p">,</span> <span class="n">S</span><span class="p">,</span> <span class="n">TOWER</span><span class="p">,</span> <span class="n">TOWER</span><span class="o">&gt;</span> <span class="p">{</span> <span class="k">static</span> <span class="k">constexpr</span> <span class="n">state</span> <span class="n">value</span> <span class="o">=</span> <span class="n">S</span><span class="p">;</span> <span class="k">static</span> <span class="k">constexpr</span> <span class="n">state</span> <span class="n">final_state</span> <span class="o">=</span> <span class="n">value</span><span class="p">;</span> <span class="k">static</span> <span class="n">std</span><span class="o">::</span><span class="n">ostream</span> <span class="o">&amp;</span><span class="n">print</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">ostream</span> <span class="o">&amp;</span><span class="n">os</span><span class="p">,</span> <span class="n">size</span> <span class="n">ndisks</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="n">os</span><span class="p">;</span> <span class="p">}</span> <span class="p">};</span></code></pre></figure> <p>Seules les versions du template pour lesquelles <code class="highlighter-rouge">SRC != TARGET</code> affichent un état. Les autres n’ont rien à afficher directement.</p> <p>Ajoutons également, par commodité, une méthode similaire dans le template <code class="highlighter-rouge">Solver</code> (sans le paramètre <code class="highlighter-rouge">ndisks</code>, car ici il est toujours égal à <code class="highlighter-rouge">DISKS</code>) :</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="k">template</span> <span class="o">&lt;</span><span class="n">size</span> <span class="n">DISKS</span><span class="p">,</span> <span class="n">state</span> <span class="n">S</span><span class="p">,</span> <span class="n">tower</span> <span class="n">TARGET</span><span class="o">&gt;</span> <span class="k">struct</span> <span class="n">Solver</span> <span class="p">{</span> <span class="k">static</span> <span class="k">constexpr</span> <span class="n">tower</span> <span class="n">src</span> <span class="o">=</span> <span class="n">getTower</span><span class="p">(</span><span class="n">S</span><span class="p">,</span> <span class="n">DISKS</span><span class="p">);</span> <span class="k">using</span> <span class="n">start</span> <span class="o">=</span> <span class="n">SolverRec</span><span class="o">&lt;</span><span class="n">DISKS</span><span class="p">,</span> <span class="n">S</span><span class="p">,</span> <span class="n">src</span><span class="p">,</span> <span class="n">TARGET</span><span class="o">&gt;</span><span class="p">;</span> <span class="k">static</span> <span class="k">constexpr</span> <span class="n">state</span> <span class="n">final_state</span> <span class="o">=</span> <span class="n">start</span><span class="o">::</span><span class="n">final_state</span><span class="p">;</span> <span class="k">static</span> <span class="n">std</span><span class="o">::</span><span class="n">ostream</span> <span class="o">&amp;</span><span class="n">print</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">ostream</span> <span class="o">&amp;</span><span class="n">os</span><span class="p">)</span> <span class="p">{</span> <span class="n">print_state</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">cout</span><span class="p">,</span> <span class="n">DISKS</span><span class="p">,</span> <span class="n">S</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span> <span class="c1">// initial state </span> <span class="k">return</span> <span class="n">start</span><span class="o">::</span><span class="n">print</span><span class="p">(</span><span class="n">os</span><span class="p">,</span> <span class="n">DISKS</span><span class="p">);</span> <span class="p">}</span> <span class="p">};</span></code></pre></figure> <p>(<a href="https://github.com/rom1v/metahanoi/blob/print/metahanoi.cpp">source</a> – <a href="https://github.com/rom1v/metahanoi/commit/print">commit</a> – tag <code class="highlighter-rouge">print</code>)</p> <p>Cette nouvelle version affiche bien lors de l’exécution les états calculés lors de la compilation.</p> <p>Cependant, les appels récursifs nécessaires à la résolution du problème ne sont pas supprimés : ils sont nécessaires à l’affichage des résultats. Il est dommage de résoudre le problème à la compilation si c’est pour que l’exécution repasse par chacune des étapes de la résolution pour l’affichage.</p> <h2 id="liste-chane">Liste chaînée</h2> <p>Pour éviter cela, nous allons générer à la compilation une liste chaînée des étapes qu’il ne restera plus qu’à parcourir à l’exécution. Chaque classe qui <em>affichait</em> un état va désormais <em>stocker</em> un nœud de la liste chainée, implémenté ainsi :</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="k">struct</span> <span class="n">ResultNode</span> <span class="p">{</span> <span class="n">state</span> <span class="n">value</span><span class="p">;</span> <span class="n">ResultNode</span> <span class="o">*</span><span class="n">next</span><span class="p">;</span> <span class="p">};</span></code></pre></figure> <p>Le défi est maintenant d’initialiser les champs <code class="highlighter-rouge">next</code> de chacun des nœuds à l’adresse du nœud suivant dans l’ordre de résolution du problème des tours de Hanoï, et non dans l’ordre des appels récursifs, qui est différent. Par exemple, l’état (<code class="highlighter-rouge">value</code>) associé à une instance du template <code class="highlighter-rouge">SolverRec</code> non spécialisé (correspondant au cas général) devra succéder à tous les états générés par l’appel récursif <code class="highlighter-rouge">rec1</code>, pourtant appelé après.</p> <p>Pour cela, chaque classe doit être capable d’indiquer à son appelant quel est le premier nœud qu’elle peut atteindre (<code class="highlighter-rouge">first</code>) et passer à chaque classe appelée le nœud qui devra suivre son nœud final (<code class="highlighter-rouge">AFTER</code>). Ces informations suffisent à déterminer dans tous les cas le nœud suivant d’une classe donnée, ce qui permet de constituer la liste chaînée complète en mémoire :</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="k">template</span> <span class="o">&lt;</span><span class="n">size</span> <span class="n">DISKS</span><span class="p">,</span> <span class="n">state</span> <span class="n">S</span><span class="p">,</span> <span class="n">tower</span> <span class="n">SRC</span><span class="p">,</span> <span class="n">tower</span> <span class="n">TARGET</span><span class="p">,</span> <span class="n">ResultNode</span> <span class="o">*</span><span class="n">AFTER</span><span class="o">&gt;</span> <span class="k">struct</span> <span class="n">SolverRec</span> <span class="p">{</span> <span class="k">static</span> <span class="n">ResultNode</span> <span class="n">node</span><span class="p">;</span> <span class="k">static</span> <span class="k">constexpr</span> <span class="n">tower</span> <span class="n">nextSrc</span> <span class="o">=</span> <span class="n">getTower</span><span class="p">(</span><span class="n">S</span><span class="p">,</span> <span class="n">DISKS</span> <span class="o">-</span> <span class="mi">1</span><span class="p">);</span> <span class="k">static</span> <span class="k">constexpr</span> <span class="n">tower</span> <span class="n">inter</span> <span class="o">=</span> <span class="n">other</span><span class="p">(</span><span class="n">SRC</span><span class="p">,</span> <span class="n">TARGET</span><span class="p">);</span> <span class="k">using</span> <span class="n">rec1</span> <span class="o">=</span> <span class="n">SolverRec</span><span class="o">&lt;</span><span class="n">DISKS</span> <span class="o">-</span> <span class="mi">1</span><span class="p">,</span> <span class="n">S</span><span class="p">,</span> <span class="n">nextSrc</span><span class="p">,</span> <span class="n">inter</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">node</span><span class="o">&gt;</span><span class="p">;</span> <span class="k">static</span> <span class="k">constexpr</span> <span class="n">state</span> <span class="n">value</span> <span class="o">=</span> <span class="n">move</span><span class="p">(</span><span class="n">rec1</span><span class="o">::</span><span class="n">final_state</span><span class="p">,</span> <span class="n">SRC</span><span class="p">,</span> <span class="n">TARGET</span><span class="p">);</span> <span class="k">using</span> <span class="n">rec2</span> <span class="o">=</span> <span class="n">SolverRec</span><span class="o">&lt;</span><span class="n">DISKS</span> <span class="o">-</span> <span class="mi">1</span><span class="p">,</span> <span class="n">value</span><span class="p">,</span> <span class="n">inter</span><span class="p">,</span> <span class="n">TARGET</span><span class="p">,</span> <span class="n">AFTER</span><span class="o">&gt;</span><span class="p">;</span> <span class="k">static</span> <span class="k">constexpr</span> <span class="n">state</span> <span class="n">final_state</span> <span class="o">=</span> <span class="n">rec2</span><span class="o">::</span><span class="n">final_state</span><span class="p">;</span> <span class="k">static</span> <span class="k">constexpr</span> <span class="n">ResultNode</span> <span class="o">*</span><span class="n">first</span> <span class="o">=</span> <span class="n">rec1</span><span class="o">::</span><span class="n">first</span><span class="p">;</span> <span class="k">static</span> <span class="k">constexpr</span> <span class="n">ResultNode</span> <span class="o">*</span><span class="n">next</span> <span class="o">=</span> <span class="n">rec2</span><span class="o">::</span><span class="n">first</span><span class="p">;</span> <span class="p">};</span> <span class="k">template</span> <span class="o">&lt;</span><span class="n">size</span> <span class="n">DISKS</span><span class="p">,</span> <span class="n">state</span> <span class="n">S</span><span class="p">,</span> <span class="n">tower</span> <span class="n">SRC</span><span class="p">,</span> <span class="n">tower</span> <span class="n">TARGET</span><span class="p">,</span> <span class="n">ResultNode</span> <span class="o">*</span><span class="n">AFTER</span><span class="o">&gt;</span> <span class="n">ResultNode</span> <span class="n">SolverRec</span><span class="o">&lt;</span><span class="n">DISKS</span><span class="p">,</span> <span class="n">S</span><span class="p">,</span> <span class="n">SRC</span><span class="p">,</span> <span class="n">TARGET</span><span class="p">,</span> <span class="n">AFTER</span><span class="o">&gt;::</span><span class="n">node</span> <span class="o">=</span> <span class="p">{</span> <span class="n">value</span><span class="p">,</span> <span class="n">next</span> <span class="p">};</span> <span class="k">template</span> <span class="o">&lt;</span><span class="n">size</span> <span class="n">DISKS</span><span class="p">,</span> <span class="n">state</span> <span class="n">S</span><span class="p">,</span> <span class="n">tower</span> <span class="n">TOWER</span><span class="p">,</span> <span class="n">ResultNode</span> <span class="o">*</span><span class="n">AFTER</span><span class="o">&gt;</span> <span class="k">struct</span> <span class="n">SolverRec</span><span class="o">&lt;</span><span class="n">DISKS</span><span class="p">,</span> <span class="n">S</span><span class="p">,</span> <span class="n">TOWER</span><span class="p">,</span> <span class="n">TOWER</span><span class="p">,</span> <span class="n">AFTER</span><span class="o">&gt;</span> <span class="p">{</span> <span class="k">static</span> <span class="k">constexpr</span> <span class="n">tower</span> <span class="n">nextSrc</span> <span class="o">=</span> <span class="n">getTower</span><span class="p">(</span><span class="n">S</span><span class="p">,</span> <span class="n">DISKS</span> <span class="o">-</span> <span class="mi">1</span><span class="p">);</span> <span class="k">using</span> <span class="n">rec</span> <span class="o">=</span> <span class="n">SolverRec</span><span class="o">&lt;</span><span class="n">DISKS</span> <span class="o">-</span> <span class="mi">1</span><span class="p">,</span> <span class="n">S</span><span class="p">,</span> <span class="n">nextSrc</span><span class="p">,</span> <span class="n">TOWER</span><span class="p">,</span> <span class="n">AFTER</span><span class="o">&gt;</span><span class="p">;</span> <span class="k">static</span> <span class="k">constexpr</span> <span class="n">state</span> <span class="n">final_state</span> <span class="o">=</span> <span class="n">rec</span><span class="o">::</span><span class="n">final_state</span><span class="p">;</span> <span class="k">static</span> <span class="k">constexpr</span> <span class="n">ResultNode</span> <span class="o">*</span><span class="n">first</span> <span class="o">=</span> <span class="n">rec</span><span class="o">::</span><span class="n">first</span><span class="p">;</span> <span class="p">};</span> <span class="k">template</span> <span class="o">&lt;</span><span class="n">state</span> <span class="n">S</span><span class="p">,</span> <span class="n">tower</span> <span class="n">SRC</span><span class="p">,</span> <span class="n">tower</span> <span class="n">TARGET</span><span class="p">,</span> <span class="n">ResultNode</span> <span class="o">*</span><span class="n">AFTER</span><span class="o">&gt;</span> <span class="k">struct</span> <span class="n">SolverRec</span><span class="o">&lt;</span><span class="mi">1</span><span class="p">,</span> <span class="n">S</span><span class="p">,</span> <span class="n">SRC</span><span class="p">,</span> <span class="n">TARGET</span><span class="p">,</span> <span class="n">AFTER</span><span class="o">&gt;</span> <span class="p">{</span> <span class="k">static</span> <span class="n">ResultNode</span> <span class="n">node</span><span class="p">;</span> <span class="k">static</span> <span class="k">constexpr</span> <span class="n">state</span> <span class="n">value</span> <span class="o">=</span> <span class="n">move</span><span class="p">(</span><span class="n">S</span><span class="p">,</span> <span class="n">SRC</span><span class="p">,</span> <span class="n">TARGET</span><span class="p">);</span> <span class="k">static</span> <span class="k">constexpr</span> <span class="n">state</span> <span class="n">final_state</span> <span class="o">=</span> <span class="n">value</span><span class="p">;</span> <span class="k">static</span> <span class="k">constexpr</span> <span class="n">ResultNode</span> <span class="o">*</span><span class="n">first</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">node</span><span class="p">;</span> <span class="k">static</span> <span class="k">constexpr</span> <span class="n">ResultNode</span> <span class="o">*</span><span class="n">next</span> <span class="o">=</span> <span class="n">AFTER</span><span class="p">;</span> <span class="p">};</span> <span class="k">template</span> <span class="o">&lt;</span><span class="n">state</span> <span class="n">S</span><span class="p">,</span> <span class="n">tower</span> <span class="n">SRC</span><span class="p">,</span> <span class="n">tower</span> <span class="n">TARGET</span><span class="p">,</span> <span class="n">ResultNode</span> <span class="o">*</span><span class="n">AFTER</span><span class="o">&gt;</span> <span class="n">ResultNode</span> <span class="n">SolverRec</span><span class="o">&lt;</span><span class="mi">1</span><span class="p">,</span> <span class="n">S</span><span class="p">,</span> <span class="n">SRC</span><span class="p">,</span> <span class="n">TARGET</span><span class="p">,</span> <span class="n">AFTER</span><span class="o">&gt;::</span><span class="n">node</span> <span class="o">=</span> <span class="p">{</span> <span class="n">value</span><span class="p">,</span> <span class="n">next</span> <span class="p">};</span> <span class="k">template</span> <span class="o">&lt;</span><span class="n">state</span> <span class="n">S</span><span class="p">,</span> <span class="n">tower</span> <span class="n">TOWER</span><span class="p">,</span> <span class="n">ResultNode</span> <span class="o">*</span><span class="n">AFTER</span><span class="o">&gt;</span> <span class="k">struct</span> <span class="n">SolverRec</span><span class="o">&lt;</span><span class="mi">1</span><span class="p">,</span> <span class="n">S</span><span class="p">,</span> <span class="n">TOWER</span><span class="p">,</span> <span class="n">TOWER</span><span class="p">,</span> <span class="n">AFTER</span><span class="o">&gt;</span> <span class="p">{</span> <span class="k">static</span> <span class="k">constexpr</span> <span class="n">state</span> <span class="n">value</span> <span class="o">=</span> <span class="n">S</span><span class="p">;</span> <span class="k">static</span> <span class="k">constexpr</span> <span class="n">state</span> <span class="n">final_state</span> <span class="o">=</span> <span class="n">value</span><span class="p">;</span> <span class="k">static</span> <span class="k">constexpr</span> <span class="n">ResultNode</span> <span class="o">*</span><span class="n">first</span> <span class="o">=</span> <span class="n">AFTER</span><span class="p">;</span> <span class="p">};</span> <span class="k">template</span> <span class="o">&lt;</span><span class="n">size</span> <span class="n">DISKS</span><span class="p">,</span> <span class="n">state</span> <span class="n">S</span><span class="p">,</span> <span class="n">tower</span> <span class="n">TARGET</span><span class="o">&gt;</span> <span class="k">struct</span> <span class="n">Solver</span> <span class="p">{</span> <span class="k">static</span> <span class="n">ResultNode</span> <span class="n">list</span><span class="p">;</span> <span class="k">static</span> <span class="k">constexpr</span> <span class="n">tower</span> <span class="n">src</span> <span class="o">=</span> <span class="n">getTower</span><span class="p">(</span><span class="n">S</span><span class="p">,</span> <span class="n">DISKS</span><span class="p">);</span> <span class="k">using</span> <span class="n">start</span> <span class="o">=</span> <span class="n">SolverRec</span><span class="o">&lt;</span><span class="n">DISKS</span><span class="p">,</span> <span class="n">S</span><span class="p">,</span> <span class="n">src</span><span class="p">,</span> <span class="n">TARGET</span><span class="p">,</span> <span class="nb">nullptr</span><span class="o">&gt;</span><span class="p">;</span> <span class="p">};</span> <span class="k">template</span> <span class="o">&lt;</span><span class="n">size</span> <span class="n">DISKS</span><span class="p">,</span> <span class="n">state</span> <span class="n">S</span><span class="p">,</span> <span class="n">tower</span> <span class="n">TARGET</span><span class="o">&gt;</span> <span class="n">ResultNode</span> <span class="n">Solver</span><span class="o">&lt;</span><span class="n">DISKS</span><span class="p">,</span> <span class="n">S</span><span class="p">,</span> <span class="n">TARGET</span><span class="o">&gt;::</span><span class="n">list</span> <span class="o">=</span> <span class="p">{</span> <span class="n">S</span><span class="p">,</span> <span class="n">start</span><span class="o">::</span><span class="n">first</span> <span class="p">};</span></code></pre></figure> <p>La variable <code class="highlighter-rouge">static</code> <code class="highlighter-rouge">node</code> n’étant pas <code class="highlighter-rouge">constexpr</code> (elle doit être adressable à l’exécution pour former la liste chaînée), elle doit être initialisée en dehors de la classe.</p> <p>Pour parcourir simplement la liste chaînée, rendons <code class="highlighter-rouge">ResultNode</code> itérable (j’implémente ici uniquement le strict minimum pour que l’<em>iterator</em> fonctionne) :</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="k">struct</span> <span class="n">ResultNode</span> <span class="p">{</span> <span class="n">state</span> <span class="n">value</span><span class="p">;</span> <span class="n">ResultNode</span> <span class="o">*</span><span class="n">next</span><span class="p">;</span> <span class="k">class</span> <span class="nc">iterator</span> <span class="p">{</span> <span class="k">const</span> <span class="n">ResultNode</span> <span class="o">*</span><span class="n">current</span><span class="p">;</span> <span class="k">public</span><span class="o">:</span> <span class="n">iterator</span><span class="p">(</span><span class="k">const</span> <span class="n">ResultNode</span> <span class="o">*</span><span class="n">current</span><span class="p">)</span> <span class="o">:</span> <span class="n">current</span><span class="p">(</span><span class="n">current</span><span class="p">)</span> <span class="p">{};</span> <span class="n">iterator</span> <span class="o">&amp;</span><span class="k">operator</span><span class="o">++</span><span class="p">()</span> <span class="p">{</span> <span class="n">current</span> <span class="o">=</span> <span class="n">current</span><span class="o">-&gt;</span><span class="n">next</span><span class="p">;</span> <span class="k">return</span> <span class="o">*</span><span class="k">this</span><span class="p">;</span> <span class="p">}</span> <span class="n">state</span> <span class="k">operator</span><span class="o">*</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="n">current</span><span class="o">-&gt;</span><span class="n">value</span><span class="p">;</span> <span class="p">}</span> <span class="kt">bool</span> <span class="k">operator</span><span class="o">==</span><span class="p">(</span><span class="k">const</span> <span class="n">iterator</span> <span class="o">&amp;</span><span class="n">o</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="n">current</span> <span class="o">==</span> <span class="n">o</span><span class="p">.</span><span class="n">current</span><span class="p">;</span> <span class="p">}</span> <span class="kt">bool</span> <span class="k">operator</span><span class="o">!=</span><span class="p">(</span><span class="k">const</span> <span class="n">iterator</span> <span class="o">&amp;</span><span class="n">o</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="o">!</span><span class="p">(</span><span class="o">*</span><span class="k">this</span> <span class="o">==</span> <span class="n">o</span><span class="p">);</span> <span class="p">}</span> <span class="p">};</span> <span class="n">iterator</span> <span class="n">begin</span><span class="p">()</span> <span class="k">const</span> <span class="p">{</span> <span class="k">return</span> <span class="n">iterator</span><span class="p">(</span><span class="k">this</span><span class="p">);</span> <span class="p">}</span> <span class="n">iterator</span> <span class="n">end</span><span class="p">()</span> <span class="k">const</span> <span class="p">{</span> <span class="k">return</span> <span class="n">iterator</span><span class="p">(</span><span class="nb">nullptr</span><span class="p">);</span> <span class="p">}</span> <span class="p">};</span></code></pre></figure> <p>La liste peut être parcourue ainsi :</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="k">using</span> <span class="n">disks</span> <span class="o">=</span> <span class="n">Disks</span><span class="o">&lt;</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="o">&gt;</span><span class="p">;</span> <span class="k">using</span> <span class="n">solver</span> <span class="o">=</span> <span class="n">Solver</span><span class="o">&lt;</span><span class="n">disks</span><span class="o">::</span><span class="n">count</span><span class="p">,</span> <span class="n">disks</span><span class="o">::</span><span class="n">packed</span><span class="p">,</span> <span class="mi">2</span><span class="o">&gt;</span><span class="p">;</span> <span class="k">for</span> <span class="p">(</span><span class="n">state</span> <span class="n">s</span> <span class="o">:</span> <span class="n">solver</span><span class="o">::</span><span class="n">list</span><span class="p">)</span> <span class="p">{</span> <span class="n">print_state</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">cout</span><span class="p">,</span> <span class="n">disks</span><span class="o">::</span><span class="n">count</span><span class="p">,</span> <span class="n">s</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span> <span class="p">}</span></code></pre></figure> <p>(<a href="https://github.com/rom1v/metahanoi/blob/nodes/metahanoi.cpp">source</a> – <a href="https://github.com/rom1v/metahanoi/commit/nodes">commit</a> – tag <code class="highlighter-rouge">nodes</code>)</p> <p>En observant le binaire généré, la liste chaînée est directement visible (ici les octets sont en <a href="https://fr.wikipedia.org/wiki/Endianness#Little_endian">little endian</a>) :</p> <div class="highlighter-rouge"><pre class="codehilite"><code>$ objdump -sj .data metahanoi metahanoi: file format elf64-x86-64 Contents of section .data: 6012a0 00000000 00000000 00000000 00000000 6012b0 00000000 00000000 00136000 00000000 n00: { 00000, &amp;n05 } 6012c0 ca000000 00000000 f0136000 00000000 n01: { 21111, &amp;n20 } 6012d0 35000000 00000000 70136000 00000000 n02: { 01222, &amp;n12 } 6012e0 16000000 00000000 30136000 00000000 n03: { 00211, &amp;n08 } 6012f0 05000000 00000000 10136000 00000000 n04: { 00012, &amp;n06 } 601300 02000000 00000000 f0126000 00000000 n05: { 00002, &amp;n04 } 601310 04000000 00000000 e0126000 00000000 n06: { 00011, &amp;n03 } 601320 18000000 00000000 40136000 00000000 n07: { 00220, &amp;n09 } 601330 15000000 00000000 20136000 00000000 n08: { 00210, &amp;n07 } 601340 1a000000 00000000 d0126000 00000000 n09: { 00222, &amp;n02 } 601350 24000000 00000000 a0136000 00000000 n10: { 01100, &amp;n15 } 601360 2e000000 00000000 80136000 00000000 n11: { 01201, &amp;n13 } 601370 34000000 00000000 60136000 00000000 n12: { 01221, &amp;n11 } 601380 2d000000 00000000 50136000 00000000 n13: { 01200, &amp;n10 } 601390 29000000 00000000 b0136000 00000000 n14: { 01112, &amp;n16 } 6013a0 26000000 00000000 90136000 00000000 n15: { 01102, &amp;n14 } 6013b0 28000000 00000000 c0126000 00000000 n16: { 01111, &amp;n01 } 6013c0 d8000000 00000000 60146000 00000000 n17: { 22000, &amp;n27 } 6013d0 c5000000 00000000 20146000 00000000 n18: { 21022, &amp;n23 } 6013e0 cc000000 00000000 00146000 00000000 n19: { 21120, &amp;n21 } 6013f0 c9000000 00000000 e0136000 00000000 n20: { 21110, &amp;n19 } 601400 ce000000 00000000 d0136000 00000000 n21: { 21122, &amp;n18 } 601410 be000000 00000000 30146000 00000000 n22: { 21001, &amp;n24 } 601420 c4000000 00000000 10146000 00000000 n23: { 21021, &amp;n22 } 601430 bd000000 00000000 c0136000 00000000 n24: { 21000, &amp;n17 } 601440 ee000000 00000000 90146000 00000000 n25: { 22211, &amp;n30 } 601450 dd000000 00000000 70146000 00000000 n26: { 22012, &amp;n28 } 601460 da000000 00000000 50146000 00000000 n27: { 22002, &amp;n26 } 601470 dc000000 00000000 40146000 00000000 n28: { 22011, &amp;n25 } 601480 f0000000 00000000 a0146000 00000000 n29: { 22220, &amp;n31 } 601490 ed000000 00000000 80146000 00000000 n30: { 22210, &amp;n29 } 6014a0 f2000000 00000000 00000000 00000000 n31: { 22222, nullptr } </code></pre></div> <p>La colonne de gauche correspond aux adresses des données. Les 4 colonnes suivantes contiennent des blocs de 4 octets, les deux premiers de chaque ligne représentant le champ <code class="highlighter-rouge">value</code> et les deux suivants le champ <code class="highlighter-rouge">next</code> de <code class="highlighter-rouge">ResultNode</code>, que j’ai réécrits de manière plus lisible à droite.</p> <h2 id="possible">Possible ?</h2> <p>Cette représentation interpelle : pourquoi ne pas stocker plus simplement les différents états dans l’ordre, plutôt que d’utiliser des indirections pour former une liste chaînée ?</p> <p>Malheureusement, je n’ai pas trouvé de solution pour stocker les états ordonnés dans un seul tableau d’entiers dès la compilation.</p> <p>Si quelqu’un y parvient, ça m’intéresse !</p> Comportement indéfini et optimisation 2014-10-22T00:41:59+02:00 http://blog.rom1v.com/2014/10/comportement-indefini-et-optimisation <p>Dans certains langages (typiquement <a href="https://fr.wikipedia.org/wiki/C_%28langage%29">C</a> et <a href="https://fr.wikipedia.org/wiki/C%2B%2B">C++</a>), la sémantique de certaines opérations est <a href="https://en.wikipedia.org/wiki/Undefined_behavior"><em>indéfinie</em></a>. Cela permet au compilateur de ne s’intéresser qu’aux cas qui sont définis (et donc de les optimiser) sans s’occuper des effets produits sur les cas indéfinis.</p> <p>C’est un concept très précieux pour améliorer sensiblement les performances. Mais cela peut avoir des effets surprenants. Si le résultat de votre programme dépend d’un <em>comportement indéfini</em> (undefined behavior) particulier, alors votre programme complet n’a pas de sens, et le compilateur a le droit de faire ce qu’il veut. Et il ne s’en prive pas !</p> <h2 id="programme-indfini">Programme indéfini</h2> <p>Par exemple, <a href="https://en.wikipedia.org/wiki/Pointer_%28computer_programming%29#cite_ref-7">déréférencer un pointeur NULL</a>) est un <em>comportement indéfini</em>. En effet, contrairement à ce que beaucoup pensent, l’exécution du programme ne va pas forcément provoquer une <a href="https://fr.wikipedia.org/wiki/Erreur_de_segmentation">erreur de segmentation</a>.</p> <p>J’ai écrit un petit programme tout simple (<code class="highlighter-rouge">undefined.c</code>) :</p> <figure class="highlight"><pre><code class="language-c" data-lang="c"><span class="cp">#include &lt;stdio.h&gt; #include &lt;malloc.h&gt; </span> <span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span> <span class="o">*</span><span class="n">argv</span><span class="p">[])</span> <span class="p">{</span> <span class="kt">int</span> <span class="o">*</span><span class="n">i</span> <span class="o">=</span> <span class="n">argc</span> <span class="o">==</span> <span class="mi">1</span> <span class="o">?</span> <span class="nb">NULL</span> <span class="o">:</span> <span class="n">malloc</span><span class="p">(</span><span class="k">sizeof</span><span class="p">(</span><span class="kt">int</span><span class="p">));</span> <span class="o">*</span><span class="n">i</span> <span class="o">=</span> <span class="mi">42</span><span class="p">;</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">i</span><span class="p">)</span> <span class="k">return</span> <span class="mi">1</span><span class="p">;</span> <span class="n">printf</span><span class="p">(</span><span class="s">"pwnd %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="o">*</span><span class="n">i</span><span class="p">);</span> <span class="k">return</span> <span class="mi">0</span><span class="p">;</span> <span class="p">}</span></code></pre></figure> <p>Si <code class="highlighter-rouge">argc</code> vaut <code class="highlighter-rouge">1</code> (c’est-à-dire si nous appelons l’exécutable sans passer d’arguments de la ligne de commande), alors le pointeur <code class="highlighter-rouge">i</code> est <code class="highlighter-rouge">NULL</code> (ligne 5).</p> <p>Cette ligne peut paraître étrange, mais elle permet de faire dépendre <code class="highlighter-rouge">i</code> d’une valeur connue uniquement à l’exécution (<code class="highlighter-rouge">argc</code>), ce qui évite au compilateur de savoir à l’avance que <code class="highlighter-rouge">i</code> est <code class="highlighter-rouge">NULL</code>.</p> <p>La ligne 6 (<code class="highlighter-rouge">*i = 42</code>) est donc incorrecte : nous n’avons pas le droit de déréférencer un pointeur <code class="highlighter-rouge">NULL</code>. Nous nous attendons donc souvent à une erreur de segmentation.</p> <p>Mais suite à ce que je viens de vous dire, admettons que ce ne soit pas le cas, et que nous arrivions quand même sur la ligne suivante (7). Ici, si <code class="highlighter-rouge">i</code> est <code class="highlighter-rouge">NULL</code>, la fonction se termine (en retournant <code class="highlighter-rouge">1</code>, ligne 8).</p> <p>Donc il n’y a donc aucun moyen d’afficher le contenu du <code class="highlighter-rouge">printf</code> ligne 9.</p> <p>Et bien… en fait, si !</p> <h2 id="excution">Exécution</h2> <p>Essayons (j’utilise <code class="highlighter-rouge">gcc 4.7.2</code> packagé dans <em>Debian Wheezy</em> en <em>amd64</em>, les résultats peuvent différer avec un autre compilateur ou une autre version de <code class="highlighter-rouge">gcc</code>) :</p> <div class="highlighter-rouge"><pre class="codehilite"><code>$ gcc -Wall undefined.c $ ./a.out # argc == 1 Erreur de segmentation $ ./a.out hello # argc == 2 pwnd 42 </code></pre></div> <p>Jusqu’ici, tout va bien. Maintenant, activons des optimisations de compilation :</p> <div class="highlighter-rouge"><pre class="codehilite"><code>$ gcc -Wall -O2 undefined.c $ ./a.out # argc == 1 pwnd 42 </code></pre></div> <p>Voilà, nous avons réussi à exécuter le <code class="highlighter-rouge">printf</code> alors que <code class="highlighter-rouge">argc == 1</code>.</p> <p>Que s’est-il passé ?</p> <h2 id="assembleur">Assembleur</h2> <p>Pour le comprendre, il faut regarder le code généré en assembleur, sans et avec optimisations.</p> <h3 id="sans-optimisation">Sans optimisation</h3> <p>Pour générer le résultat de la compilation sans assembler (et donc obtenir un fichier source assembleur <code class="highlighter-rouge">undefined.s</code>) :</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash">gcc -S undefined.c</code></pre></figure> <p>J’ai commenté les parties importantes :</p> <figure class="highlight"><pre><code class="language-nasm" data-lang="nasm"> <span class="p">.</span><span class="n">file</span> <span class="s">"undefined.c"</span> <span class="p">.</span><span class="kr">section</span> <span class="p">.</span><span class="n">rodata</span> <span class="p">.</span><span class="n">LC0</span><span class="o">:</span> <span class="p">.</span><span class="n">string</span> <span class="s">"pwnd %d</span><span class="se">\n</span><span class="s">"</span> <span class="p">.</span><span class="n">text</span> <span class="p">.</span><span class="n">globl</span> <span class="n">main</span> <span class="p">.</span><span class="n">type</span> <span class="n">main</span><span class="p">,</span> <span class="err">@</span><span class="n">function</span> <span class="n">main</span><span class="o">:</span> <span class="p">.</span><span class="n">LFB0</span><span class="o">:</span> <span class="p">.</span><span class="n">cfi_startproc</span> <span class="n">pushq</span> <span class="err">%</span><span class="n">rbp</span> <span class="p">.</span><span class="n">cfi_def_cfa_offset</span> <span class="mi">16</span> <span class="p">.</span><span class="n">cfi_offset</span> <span class="mi">6</span><span class="p">,</span> <span class="o">-</span><span class="mi">16</span> <span class="k">movq</span> <span class="err">%</span><span class="n">rsp</span><span class="p">,</span> <span class="err">%</span><span class="n">rbp</span> <span class="p">.</span><span class="n">cfi_def_cfa_register</span> <span class="mi">6</span> <span class="n">subq</span> <span class="err">$</span><span class="mi">32</span><span class="p">,</span> <span class="err">%</span><span class="n">rsp</span> <span class="n">movl</span> <span class="err">%</span><span class="n">edi</span><span class="p">,</span> <span class="o">-</span><span class="mi">20</span><span class="p">(</span><span class="err">%</span><span class="n">rbp</span><span class="p">)</span> <span class="k">movq</span> <span class="err">%</span><span class="n">rsi</span><span class="p">,</span> <span class="o">-</span><span class="mi">32</span><span class="p">(</span><span class="err">%</span><span class="n">rbp</span><span class="p">)</span> <span class="n">cmpl</span> <span class="err">$</span><span class="mi">1</span><span class="p">,</span> <span class="o">-</span><span class="mi">20</span><span class="p">(</span><span class="err">%</span><span class="n">rbp</span><span class="p">)</span> <span class="c">; if (argc == 1)</span> <span class="k">je</span> <span class="p">.</span><span class="n">L2</span> <span class="c">; goto .L2</span> <span class="n">movl</span> <span class="err">$</span><span class="mi">4</span><span class="p">,</span> <span class="err">%</span><span class="n">edi</span> <span class="c">; arg0 = 4 // sizeof(int)</span> <span class="k">call</span> <span class="n">malloc</span> <span class="c">; tmp = malloc(4)</span> <span class="k">jmp</span> <span class="p">.</span><span class="n">L3</span> <span class="c">; goto .L3</span> <span class="p">.</span><span class="n">L2</span><span class="o">:</span> <span class="n">movl</span> <span class="err">$</span><span class="mi">0</span><span class="p">,</span> <span class="err">%</span><span class="n">eax</span> <span class="p">.</span><span class="n">L3</span><span class="o">:</span> <span class="k">movq</span> <span class="err">%</span><span class="n">rax</span><span class="p">,</span> <span class="o">-</span><span class="mi">8</span><span class="p">(</span><span class="err">%</span><span class="n">rbp</span><span class="p">)</span> <span class="c">; i = tmp</span> <span class="k">movq</span> <span class="o">-</span><span class="mi">8</span><span class="p">(</span><span class="err">%</span><span class="n">rbp</span><span class="p">),</span> <span class="err">%</span><span class="n">rax</span> <span class="n">movl</span> <span class="err">$</span><span class="mi">42</span><span class="p">,</span> <span class="p">(</span><span class="err">%</span><span class="n">rax</span><span class="p">)</span> <span class="c">; *i = 42</span> <span class="n">cmpq</span> <span class="err">$</span><span class="mi">0</span><span class="p">,</span> <span class="o">-</span><span class="mi">8</span><span class="p">(</span><span class="err">%</span><span class="n">rbp</span><span class="p">)</span> <span class="c">; if (!i)</span> <span class="k">jne</span> <span class="p">.</span><span class="n">L4</span> <span class="c">; goto .L4</span> <span class="n">movl</span> <span class="err">$</span><span class="mi">1</span><span class="p">,</span> <span class="err">%</span><span class="n">eax</span> <span class="c">; ret = 1</span> <span class="k">jmp</span> <span class="p">.</span><span class="n">L5</span> <span class="p">.</span><span class="n">L4</span><span class="o">:</span> <span class="k">movq</span> <span class="o">-</span><span class="mi">8</span><span class="p">(</span><span class="err">%</span><span class="n">rbp</span><span class="p">),</span> <span class="err">%</span><span class="n">rax</span> <span class="n">movl</span> <span class="p">(</span><span class="err">%</span><span class="n">rax</span><span class="p">),</span> <span class="err">%</span><span class="n">eax</span> <span class="n">movl</span> <span class="err">%</span><span class="n">eax</span><span class="p">,</span> <span class="err">%</span><span class="n">esi</span> <span class="c">; arg1 = *i</span> <span class="n">movl</span> <span class="err">$</span><span class="p">.</span><span class="n">LC0</span><span class="p">,</span> <span class="err">%</span><span class="n">edi</span> <span class="c">; arg0 points to "pwnd %d\n"</span> <span class="n">movl</span> <span class="err">$</span><span class="mi">0</span><span class="p">,</span> <span class="err">%</span><span class="n">eax</span> <span class="k">call</span> <span class="n">printf</span> <span class="c">; printf("pwnd %d\n", *i)</span> <span class="n">movl</span> <span class="err">$</span><span class="mi">0</span><span class="p">,</span> <span class="err">%</span><span class="n">eax</span> <span class="c">; ret = 0</span> <span class="p">.</span><span class="n">L5</span><span class="o">:</span> <span class="k">leave</span> <span class="p">.</span><span class="n">cfi_def_cfa</span> <span class="mi">7</span><span class="p">,</span> <span class="mi">8</span> <span class="k">ret</span> <span class="c">; return ret</span> <span class="p">.</span><span class="n">cfi_endproc</span> <span class="p">.</span><span class="n">LFE0</span><span class="o">:</span> <span class="p">.</span><span class="n">size</span> <span class="n">main</span><span class="p">,</span> <span class="p">.</span><span class="o">-</span><span class="n">main</span> <span class="p">.</span><span class="n">ident</span> <span class="s">"GCC: (Debian 4.7.2-5) 4.7.2"</span> <span class="p">.</span><span class="kr">section</span> <span class="p">.</span><span class="n">note</span><span class="p">.</span><span class="n">GNU</span><span class="o">-</span><span class="n">stack</span><span class="p">,</span><span class="s">""</span><span class="p">,</span><span class="err">@</span><span class="n">progbits</span></code></pre></figure> <p>Le code généré est très fidèle au code source C.</p> <h3 id="avec-gcc--o">Avec <code class="highlighter-rouge">gcc -O</code></h3> <p>Maintenant, activons certaines optimisations :</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash">gcc -O -S undefined.c</code></pre></figure> <p>Ce qui donne :</p> <figure class="highlight"><pre><code class="language-nasm" data-lang="nasm"> <span class="p">.</span><span class="n">file</span> <span class="s">"undefined.c"</span> <span class="p">.</span><span class="kr">section</span> <span class="p">.</span><span class="n">rodata</span><span class="p">.</span><span class="n">str1</span><span class="p">.</span><span class="mi">1</span><span class="p">,</span><span class="s">"aMS"</span><span class="p">,</span><span class="err">@</span><span class="n">progbits</span><span class="p">,</span><span class="mi">1</span> <span class="p">.</span><span class="n">LC0</span><span class="o">:</span> <span class="p">.</span><span class="n">string</span> <span class="s">"pwnd %d</span><span class="se">\n</span><span class="s">"</span> <span class="p">.</span><span class="n">text</span> <span class="p">.</span><span class="n">globl</span> <span class="n">main</span> <span class="p">.</span><span class="n">type</span> <span class="n">main</span><span class="p">,</span> <span class="err">@</span><span class="n">function</span> <span class="n">main</span><span class="o">:</span> <span class="p">.</span><span class="n">LFB11</span><span class="o">:</span> <span class="p">.</span><span class="n">cfi_startproc</span> <span class="n">cmpl</span> <span class="err">$</span><span class="mi">1</span><span class="p">,</span> <span class="err">%</span><span class="n">edi</span> <span class="c">; if (argc == 1)</span> <span class="k">je</span> <span class="p">.</span><span class="n">L2</span> <span class="c">; goto .L2</span> <span class="n">subq</span> <span class="err">$</span><span class="mi">8</span><span class="p">,</span> <span class="err">%</span><span class="n">rsp</span> <span class="p">.</span><span class="n">cfi_def_cfa_offset</span> <span class="mi">16</span> <span class="n">movl</span> <span class="err">$</span><span class="mi">4</span><span class="p">,</span> <span class="err">%</span><span class="n">edi</span> <span class="c">; arg0 = 4 // sizeof(int)</span> <span class="k">call</span> <span class="n">malloc</span> <span class="c">; tmp = malloc(4)</span> <span class="k">movq</span> <span class="err">%</span><span class="n">rax</span><span class="p">,</span> <span class="err">%</span><span class="n">rdx</span> <span class="c">; i = tmp</span> <span class="n">movl</span> <span class="err">$</span><span class="mi">42</span><span class="p">,</span> <span class="p">(</span><span class="err">%</span><span class="n">rax</span><span class="p">)</span> <span class="c">; *i = 42</span> <span class="n">movl</span> <span class="err">$</span><span class="mi">1</span><span class="p">,</span> <span class="err">%</span><span class="n">eax</span> <span class="c">; ret = 1</span> <span class="n">testq</span> <span class="err">%</span><span class="n">rdx</span><span class="p">,</span> <span class="err">%</span><span class="n">rdx</span> <span class="c">; if (!i)</span> <span class="k">je</span> <span class="p">.</span><span class="n">L5</span> <span class="c">; goto .L5</span> <span class="n">movl</span> <span class="err">$</span><span class="mi">42</span><span class="p">,</span> <span class="err">%</span><span class="n">esi</span> <span class="c">; arg1 = 42</span> <span class="n">movl</span> <span class="err">$</span><span class="p">.</span><span class="n">LC0</span><span class="p">,</span> <span class="err">%</span><span class="n">edi</span> <span class="c">; arg0 points to "pwnd %d\n"</span> <span class="n">movl</span> <span class="err">$</span><span class="mi">0</span><span class="p">,</span> <span class="err">%</span><span class="n">eax</span> <span class="k">call</span> <span class="n">printf</span> <span class="c">; printf("pwnd %d\n", 42)</span> <span class="n">movl</span> <span class="err">$</span><span class="mi">0</span><span class="p">,</span> <span class="err">%</span><span class="n">eax</span> <span class="c">; ret = 0</span> <span class="k">jmp</span> <span class="p">.</span><span class="n">L5</span> <span class="c">; goto .L5</span> <span class="p">.</span><span class="n">L2</span><span class="o">:</span> <span class="p">.</span><span class="n">cfi_def_cfa_offset</span> <span class="mi">8</span> <span class="n">movl</span> <span class="err">$</span><span class="mi">42</span><span class="p">,</span> <span class="mi">0</span> <span class="c">; segfault (dereference addr 0)</span> <span class="n">movl</span> <span class="err">$</span><span class="mi">1</span><span class="p">,</span> <span class="err">%</span><span class="n">eax</span> <span class="c">; ret = 1</span> <span class="k">ret</span> <span class="p">.</span><span class="n">L5</span><span class="o">:</span> <span class="p">.</span><span class="n">cfi_def_cfa_offset</span> <span class="mi">16</span> <span class="n">addq</span> <span class="err">$</span><span class="mi">8</span><span class="p">,</span> <span class="err">%</span><span class="n">rsp</span> <span class="p">.</span><span class="n">cfi_def_cfa_offset</span> <span class="mi">8</span> <span class="k">ret</span> <span class="c">; return ret</span> <span class="p">.</span><span class="n">cfi_endproc</span> <span class="p">.</span><span class="n">LFE11</span><span class="o">:</span> <span class="p">.</span><span class="n">size</span> <span class="n">main</span><span class="p">,</span> <span class="p">.</span><span class="o">-</span><span class="n">main</span> <span class="p">.</span><span class="n">ident</span> <span class="s">"GCC: (Debian 4.7.2-5) 4.7.2"</span> <span class="p">.</span><span class="kr">section</span> <span class="p">.</span><span class="n">note</span><span class="p">.</span><span class="n">GNU</span><span class="o">-</span><span class="n">stack</span><span class="p">,</span><span class="s">""</span><span class="p">,</span><span class="err">@</span><span class="n">progbits</span></code></pre></figure> <p>Là, le compilateur a réorganisé le code. Si je devais le retraduire en C, j’écrirais ceci :</p> <figure class="highlight"><pre><code class="language-c" data-lang="c"><span class="cp">#include &lt;stdio.h&gt; #include &lt;malloc.h&gt; </span> <span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span> <span class="o">*</span><span class="n">argv</span><span class="p">[])</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="n">argc</span> <span class="o">==</span> <span class="mi">1</span><span class="p">)</span> <span class="o">*</span><span class="p">((</span><span class="kt">int</span> <span class="o">*</span><span class="p">)</span> <span class="nb">NULL</span><span class="p">)</span> <span class="o">=</span> <span class="mi">42</span><span class="p">;</span> <span class="kt">int</span> <span class="o">*</span><span class="n">i</span> <span class="o">=</span> <span class="n">malloc</span><span class="p">(</span><span class="k">sizeof</span><span class="p">(</span><span class="kt">int</span><span class="p">));</span> <span class="o">*</span><span class="n">i</span> <span class="o">=</span> <span class="mi">42</span><span class="p">;</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">i</span><span class="p">)</span> <span class="k">return</span> <span class="mi">1</span><span class="p">;</span> <span class="n">printf</span><span class="p">(</span><span class="s">"pwnd %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="mi">42</span><span class="p">);</span> <span class="k">return</span> <span class="mi">0</span><span class="p">;</span> <span class="p">}</span></code></pre></figure> <p>Ce qui est amusant, c’est qu’il alloue de la mémoire pour stocker <code class="highlighter-rouge">i</code>, il lui affecte la valeur <code class="highlighter-rouge">42</code>… mais ne la lit jamais. En effet, il a décidé de recoder en dur <code class="highlighter-rouge">42</code> pour le paramètre du <code class="highlighter-rouge">printf</code>.</p> <p>Mais avec ce résultat, impossible d’atteindre le <code class="highlighter-rouge">printf</code> si <code class="highlighter-rouge">argc == 1</code>.</p> <h3 id="avec-gcc--o2">Avec <code class="highlighter-rouge">gcc -O2</code></h3> <p>Optimisons davantage :</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash">gcc -O2 -S undefined.c</code></pre></figure> <p>Ou, plus précisément (avec <code class="highlighter-rouge">gcc 4.9.1</code> par exemple, l’option <code class="highlighter-rouge">-O2</code> ne suffit pas) :</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash">gcc -O -ftree-vrp -fdelete-null-pointer-checks -S undefined.c</code></pre></figure> <p>(les options d’optimisation sont décrites dans la <a href="https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html">doc</a>).</p> <figure class="highlight"><pre><code class="language-nasm" data-lang="nasm"> <span class="p">.</span><span class="n">file</span> <span class="s">"undefined.c"</span> <span class="p">.</span><span class="kr">section</span> <span class="p">.</span><span class="n">rodata</span><span class="p">.</span><span class="n">str1</span><span class="p">.</span><span class="mi">1</span><span class="p">,</span><span class="s">"aMS"</span><span class="p">,</span><span class="err">@</span><span class="n">progbits</span><span class="p">,</span><span class="mi">1</span> <span class="p">.</span><span class="n">LC0</span><span class="o">:</span> <span class="p">.</span><span class="n">string</span> <span class="s">"pwnd %d</span><span class="se">\n</span><span class="s">"</span> <span class="p">.</span><span class="kr">section</span> <span class="p">.</span><span class="n">text</span><span class="p">.</span><span class="n">startup</span><span class="p">,</span><span class="s">"ax"</span><span class="p">,</span><span class="err">@</span><span class="n">progbits</span> <span class="p">.</span><span class="n">p2align</span> <span class="mi">4</span><span class="p">,,</span><span class="mi">15</span> <span class="p">.</span><span class="n">globl</span> <span class="n">main</span> <span class="p">.</span><span class="n">type</span> <span class="n">main</span><span class="p">,</span> <span class="err">@</span><span class="n">function</span> <span class="n">main</span><span class="o">:</span> <span class="p">.</span><span class="n">LFB11</span><span class="o">:</span> <span class="p">.</span><span class="n">cfi_startproc</span> <span class="n">subq</span> <span class="err">$</span><span class="mi">8</span><span class="p">,</span> <span class="err">%</span><span class="n">rsp</span> <span class="p">.</span><span class="n">cfi_def_cfa_offset</span> <span class="mi">16</span> <span class="n">movl</span> <span class="err">$</span><span class="mi">42</span><span class="p">,</span> <span class="err">%</span><span class="n">esi</span> <span class="c">; arg1 = 42</span> <span class="n">movl</span> <span class="err">$</span><span class="p">.</span><span class="n">LC0</span><span class="p">,</span> <span class="err">%</span><span class="n">edi</span> <span class="c">; arg2 points to "pwnd %d\n"</span> <span class="n">xorl</span> <span class="err">%</span><span class="n">eax</span><span class="p">,</span> <span class="err">%</span><span class="n">eax</span> <span class="k">call</span> <span class="n">printf</span> <span class="c">; printf("pwnd %d\n", 42)</span> <span class="n">xorl</span> <span class="err">%</span><span class="n">eax</span><span class="p">,</span> <span class="err">%</span><span class="n">eax</span> <span class="c">; ret = 0</span> <span class="n">addq</span> <span class="err">$</span><span class="mi">8</span><span class="p">,</span> <span class="err">%</span><span class="n">rsp</span> <span class="p">.</span><span class="n">cfi_def_cfa_offset</span> <span class="mi">8</span> <span class="k">ret</span> <span class="c">; return ret</span> <span class="p">.</span><span class="n">cfi_endproc</span> <span class="p">.</span><span class="n">LFE11</span><span class="o">:</span> <span class="p">.</span><span class="n">size</span> <span class="n">main</span><span class="p">,</span> <span class="p">.</span><span class="o">-</span><span class="n">main</span> <span class="p">.</span><span class="n">ident</span> <span class="s">"GCC: (Debian 4.7.2-5) 4.7.2"</span> <span class="p">.</span><span class="kr">section</span> <span class="p">.</span><span class="n">note</span><span class="p">.</span><span class="n">GNU</span><span class="o">-</span><span class="n">stack</span><span class="p">,</span><span class="s">""</span><span class="p">,</span><span class="err">@</span><span class="n">progbits</span></code></pre></figure> <p>Là, l’optimisation donne un résultat beaucoup plus direct :</p> <figure class="highlight"><pre><code class="language-c" data-lang="c"><span class="cp">#include &lt;stdio.h&gt; </span> <span class="kt">int</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span> <span class="n">printf</span><span class="p">(</span><span class="s">"pwnd %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="mi">42</span><span class="p">);</span> <span class="k">return</span> <span class="mi">0</span><span class="p">;</span> <span class="p">}</span></code></pre></figure> <p>Quel raisonnement a-t-il pu suivre pour obtenir ce résultat ? Par exemple le suivant.</p> <p>Lorsqu’il rencontre la ligne 6 de <code class="highlighter-rouge">undefined.c</code>, soit <code class="highlighter-rouge">i</code> est <code class="highlighter-rouge">NULL</code>, soit <code class="highlighter-rouge">i</code> n’est pas <code class="highlighter-rouge">NULL</code>. Le compilateur sait que déréférencer un pointeur <code class="highlighter-rouge">NULL</code> est <em>indéfini</em>. Il n’a donc pas à gérer ce cas. Il considère donc que <code class="highlighter-rouge">i</code> est forcément non-<code class="highlighter-rouge">NULL</code>.</p> <p>Mais alors, à quoi bon tester si <code class="highlighter-rouge">i</code> est non-<code class="highlighter-rouge">NULL</code> ligne 7 ? Le test ne sert à rien. Donc il le supprime.</p> <p>Ce raisonnement permet de transformer le code ainsi :</p> <figure class="highlight"><pre><code class="language-c" data-lang="c"><span class="cp">#include &lt;stdio.h&gt; #include &lt;malloc.h&gt; </span> <span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span> <span class="o">*</span><span class="n">argv</span><span class="p">[])</span> <span class="p">{</span> <span class="kt">int</span> <span class="o">*</span><span class="n">i</span> <span class="o">=</span> <span class="n">argc</span> <span class="o">==</span> <span class="mi">1</span> <span class="o">?</span> <span class="nb">NULL</span> <span class="o">:</span> <span class="n">malloc</span><span class="p">(</span><span class="k">sizeof</span><span class="p">(</span><span class="kt">int</span><span class="p">));</span> <span class="o">*</span><span class="n">i</span> <span class="o">=</span> <span class="mi">42</span><span class="p">;</span> <span class="n">printf</span><span class="p">(</span><span class="s">"pwnd %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="o">*</span><span class="n">i</span><span class="p">);</span> <span class="k">return</span> <span class="mi">0</span><span class="p">;</span> <span class="p">}</span></code></pre></figure> <p>Mais ce n’est pas tout. Le compilateur sait que <code class="highlighter-rouge">i</code> n’est pas <code class="highlighter-rouge">NULL</code>, donc il peut considérer que le <code class="highlighter-rouge">malloc</code> a lieu. Et allouer un entier en mémoire, écrire <code class="highlighter-rouge">42</code> dedans, puis lire la valeur cet entier plus tard, ça se simplifie beaucoup : juste lire <code class="highlighter-rouge">42</code>, sans allouer de mémoire.</p> <p>Ce qu’il simplifie en :</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nb">printf</span><span class="o">(</span><span class="s2">"pwnd %d</span><span class="se">\n</span><span class="s2">"</span>, 42<span class="o">)</span>;</code></pre></figure> <p>CQFD.</p> <h3 id="avec-clang--02">Avec <code class="highlighter-rouge">clang -02</code></h3> <p>Il est intéressant d’observer ce que produit un autre compilateur : <a href="https://fr.wikipedia.org/wiki/Clang">Clang</a>.</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash">clang -O2 -S undefined.c</code></pre></figure> <p>Voici le résultat :</p> <figure class="highlight"><pre><code class="language-nasm" data-lang="nasm"> <span class="p">.</span><span class="n">file</span> <span class="s">"undefined.c"</span> <span class="p">.</span><span class="n">text</span> <span class="p">.</span><span class="n">globl</span> <span class="n">main</span> <span class="p">.</span><span class="n">align</span> <span class="mi">16</span><span class="p">,</span> <span class="mh">0x90</span> <span class="p">.</span><span class="n">type</span> <span class="n">main</span><span class="p">,</span><span class="err">@</span><span class="n">function</span> <span class="n">main</span><span class="o">:</span> <span class="err">#</span> <span class="err">@</span><span class="n">main</span> <span class="p">.</span><span class="n">Ltmp2</span><span class="o">:</span> <span class="p">.</span><span class="n">cfi_startproc</span> <span class="err">#</span> <span class="n">BB</span><span class="err">#</span><span class="mi">0</span><span class="o">:</span> <span class="n">pushq</span> <span class="err">%</span><span class="n">rbp</span> <span class="p">.</span><span class="n">Ltmp3</span><span class="o">:</span> <span class="p">.</span><span class="n">cfi_def_cfa_offset</span> <span class="mi">16</span> <span class="p">.</span><span class="n">Ltmp4</span><span class="o">:</span> <span class="p">.</span><span class="n">cfi_offset</span> <span class="err">%</span><span class="n">rbp</span><span class="p">,</span> <span class="o">-</span><span class="mi">16</span> <span class="k">movq</span> <span class="err">%</span><span class="n">rsp</span><span class="p">,</span> <span class="err">%</span><span class="n">rbp</span> <span class="p">.</span><span class="n">Ltmp5</span><span class="o">:</span> <span class="p">.</span><span class="n">cfi_def_cfa_register</span> <span class="err">%</span><span class="n">rbp</span> <span class="n">cmpl</span> <span class="err">$</span><span class="mi">1</span><span class="p">,</span> <span class="err">%</span><span class="n">edi</span> <span class="c">; if (argc == 1)</span> <span class="k">je</span> <span class="p">.</span><span class="n">LBB0_4</span> <span class="c">; goto .LBB0_4</span> <span class="err">#</span> <span class="n">BB</span><span class="err">#</span><span class="mi">1</span><span class="o">:</span> <span class="n">movl</span> <span class="err">$</span><span class="mi">4</span><span class="p">,</span> <span class="err">%</span><span class="n">edi</span> <span class="c">; arg0 = 4 //sizeof(int)</span> <span class="n">callq</span> <span class="n">malloc</span> <span class="c">; tmp = malloc(4)</span> <span class="k">movq</span> <span class="err">%</span><span class="n">rax</span><span class="p">,</span> <span class="err">%</span><span class="n">rcx</span> <span class="c">; i = tmp</span> <span class="n">movl</span> <span class="err">$</span><span class="mi">42</span><span class="p">,</span> <span class="p">(</span><span class="err">%</span><span class="n">rcx</span><span class="p">)</span> <span class="c">; *i = 42</span> <span class="n">movl</span> <span class="err">$</span><span class="mi">1</span><span class="p">,</span> <span class="err">%</span><span class="n">eax</span> <span class="c">; ret = 1</span> <span class="n">testq</span> <span class="err">%</span><span class="n">rcx</span><span class="p">,</span> <span class="err">%</span><span class="n">rcx</span> <span class="c">; if (!i)</span> <span class="k">je</span> <span class="p">.</span><span class="n">LBB0_3</span> <span class="c">; goto .LBB0_3</span> <span class="err">#</span> <span class="n">BB</span><span class="err">#</span><span class="mi">2</span><span class="o">:</span> <span class="n">movl</span> <span class="err">$</span><span class="p">.</span><span class="n">L</span><span class="p">.</span><span class="k">str</span><span class="p">,</span> <span class="err">%</span><span class="n">edi</span> <span class="c">; arg0 points to "pwnd %d\n"</span> <span class="n">movl</span> <span class="err">$</span><span class="mi">42</span><span class="p">,</span> <span class="err">%</span><span class="n">esi</span> <span class="c">; arg1 = 42</span> <span class="n">xorb</span> <span class="err">%</span><span class="n">al</span><span class="p">,</span> <span class="err">%</span><span class="n">al</span> <span class="n">callq</span> <span class="n">printf</span> <span class="c">; printf("pwnd %d\n", *i)</span> <span class="n">xorl</span> <span class="err">%</span><span class="n">eax</span><span class="p">,</span> <span class="err">%</span><span class="n">eax</span> <span class="c">; ret = 0</span> <span class="p">.</span><span class="n">LBB0_3</span><span class="o">:</span> <span class="n">popq</span> <span class="err">%</span><span class="n">rbp</span> <span class="k">ret</span> <span class="c">; return ret</span> <span class="p">.</span><span class="n">LBB0_4</span><span class="o">:</span> <span class="err">#</span> <span class="err">%</span><span class="p">.</span><span class="n">thread</span> <span class="k">ud2</span> <span class="c">; undefined instruction</span> <span class="p">.</span><span class="n">Ltmp6</span><span class="o">:</span> <span class="p">.</span><span class="n">size</span> <span class="n">main</span><span class="p">,</span> <span class="p">.</span><span class="n">Ltmp6</span><span class="o">-</span><span class="n">main</span> <span class="p">.</span><span class="n">Ltmp7</span><span class="o">:</span> <span class="p">.</span><span class="n">cfi_endproc</span> <span class="p">.</span><span class="n">Leh_func_end0</span><span class="o">:</span> <span class="p">.</span><span class="n">type</span> <span class="p">.</span><span class="n">L</span><span class="p">.</span><span class="k">str</span><span class="p">,</span><span class="err">@</span><span class="n">object</span> <span class="err">#</span> <span class="err">@</span><span class="p">.</span><span class="k">str</span> <span class="p">.</span><span class="kr">section</span> <span class="p">.</span><span class="n">rodata</span><span class="p">.</span><span class="n">str1</span><span class="p">.</span><span class="mi">1</span><span class="p">,</span><span class="s">"aMS"</span><span class="p">,</span><span class="err">@</span><span class="n">progbits</span><span class="p">,</span><span class="mi">1</span> <span class="p">.</span><span class="n">L</span><span class="p">.</span><span class="k">str</span><span class="o">:</span> <span class="p">.</span><span class="n">asciz</span> <span class="s">"pwnd %d</span><span class="se">\n</span><span class="s">"</span> <span class="p">.</span><span class="n">size</span> <span class="p">.</span><span class="n">L</span><span class="p">.</span><span class="k">str</span><span class="p">,</span> <span class="mi">9</span> <span class="p">.</span><span class="kr">section</span> <span class="s">".note.GNU-stack"</span><span class="p">,</span><span class="s">""</span><span class="p">,</span><span class="err">@</span><span class="n">progbits</span></code></pre></figure> <p>Il réalise les mêmes optimisations que <code class="highlighter-rouge">gcc -O</code>, sauf qu’il génère une erreur explicite grâce à l’instruction machine <a href="https://en.wikipedia.org/wiki/X86_instruction_listings#Added_with_Pentium_Pro"><code class="highlighter-rouge">ud2</code></a>.</p> <figure class="highlight"><pre><code class="language-c" data-lang="c"><span class="cp">#include &lt;stdio.h&gt; #include &lt;malloc.h&gt; </span> <span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span> <span class="o">*</span><span class="n">argv</span><span class="p">[])</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="n">argc</span> <span class="o">==</span> <span class="mi">1</span><span class="p">)</span> <span class="n">ud2</span><span class="p">();</span> <span class="cm">/* hardware undefined instruction */</span> <span class="kt">int</span> <span class="o">*</span><span class="n">i</span> <span class="o">=</span> <span class="n">malloc</span><span class="p">(</span><span class="k">sizeof</span><span class="p">(</span><span class="kt">int</span><span class="p">));</span> <span class="o">*</span><span class="n">i</span> <span class="o">=</span> <span class="mi">42</span><span class="p">;</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">i</span><span class="p">)</span> <span class="k">return</span> <span class="mi">1</span><span class="p">;</span> <span class="n">printf</span><span class="p">(</span><span class="s">"pwnd %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="mi">42</span><span class="p">);</span> <span class="k">return</span> <span class="mi">0</span><span class="p">;</span> <span class="p">}</span></code></pre></figure> <p>Étonnamment, <em>Clang</em> ne prend jamais la décision de supprimer le <code class="highlighter-rouge">malloc</code>.</p> <p>Par contre, avec une version suffisamment récente (ça marche avec <em>Clang 3.5.0</em>), il est possible d’ajouter des <a href="http://clang.llvm.org/docs/UsersManual.html#controlling-code-generation">vérifications lors de l’exécution</a> :</p> <div class="highlighter-rouge"><pre class="codehilite"><code>$ clang -fsanitize=null undefined.c &amp;&amp; ./a.out undefined.c:6:5: runtime error: store to null pointer of type 'int' Erreur de segmentation </code></pre></div> <p>Ça peut être pratique pour détecter des problèmes. Et puis des <a href="http://docs.oracle.com/javase/7/docs/api/java/lang/NullPointerException.html"><code class="highlighter-rouge">NullPointerException</code></a>s en C, ça fait rêver, non ?</p> <h2 id="retenir">À retenir</h2> <p>Si un programme contient un <em>comportement indéfini</em>, alors son comportement <strong>est</strong> <em>indéfini</em>. Pas juste la ligne en question. Pas juste les lignes qui suivent la ligne en question. Le programme. Même s’il fonctionne maintenant sur votre machine avec votre version de compilateur.</p> <blockquote> <p>Somebody once told me that in basketball you can’t hold the ball and run. I got a basketball and tried it and it worked just fine. He obviously didn’t understand basketball.</p> </blockquote> <p>(<a href="http://www.eskimo.com/~scs/readings/undef.950311.html">source</a>)</p> <p>Pour aller plus loin et étudier d’autres exemples, je vous recommande la lecture des articles suivants (en anglais) :</p> <ul> <li><a href="http://blog.regehr.org/archives/213">A Guide to Undefined Behavior in C and C++</a> | <a href="http://blog.regehr.org/archives/226">2</a> | <a href="http://blog.regehr.org/archives/232">3</a></li> <li><a href="http://blog.llvm.org/2011/05/what-every-c-programmer-should-know.html">What Every C Programmer Should Know About Undefined Behavior</a> | <a href="http://blog.llvm.org/2011/05/what-every-c-programmer-should-know_14.html">2</a> | <a href="http://blog.llvm.org/2011/05/what-every-c-programmer-should-know_21.html">3</a></li> <li><a href="http://pdos.csail.mit.edu/papers/ub:apsys12.pdf">Undefined Behavior: What Happened to My Code?</a> (pdf)</li> </ul> <h2 id="optimisations-multi-threades">Optimisations multi-threadées</h2> <p>Les <em>comportements indéfinis</em> font partie intégrante du <em>C</em> et du <em>C++</em>. Mais même dans des langages de plus haut niveau, il existe des comportements <em>indéfinis</em> (pas de même nature, je vous l’accorde), notamment lorsque plusieurs <a href="https://fr.wikipedia.org/wiki/Thread_%28informatique%29">threads</a> s’exécutent en parallèle.</p> <p>Pour garantir certains comportements, il faut utiliser des mécanismes de <a href="https://en.wikipedia.org/wiki/Synchronization_%28computer_science%29">synchronisation</a>. Dans une vie antérieure, j’avais <a href="http://rom.developpez.com/java-synchronisation/">présenté</a> certains de ces mécanismes en <a href="https://fr.wikipedia.org/wiki/Java_%28langage%29">Java</a>.</p> <p>Mais une erreur courante est de penser que la synchronisation ne fait que garantir l’<a href="https://fr.wikipedia.org/wiki/Atomicit%C3%A9_%28informatique%29">atomicité</a> avec des <a href="https://fr.wikipedia.org/wiki/Section_critique">sections critiques</a>. En réalité, c’est plus complexe que cela. D’une part, elle ajoute des <a href="https://en.wikipedia.org/wiki/Memory_barrier">barrières mémoire</a> empêchant certaines réorganisations des instructions (ce qui explique pourquoi le <a href="https://fr.wikipedia.org/wiki/Double-checked_locking">double-checked locking</a> pour écrire des <a href="https://fr.wikipedia.org/wiki/Singleton_%28patron_de_conception%29">singletons</a> est <a href="http://www.javamex.com/tutorials/double_checked_locking.shtml">faux</a>). D’autre part, elle permet de synchroniser les caches locaux des threads, sans quoi l’exemple suivant (inspiré d’<a href="http://stackoverflow.com/questions/5022100/when-does-java-thread-cache-refresh-happens/5022188#5022188">ici</a>) est incorrect :</p> <figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">class</span> <span class="nc">VisibilityTest</span> <span class="kd">extends</span> <span class="n">Thread</span> <span class="o">{</span> <span class="kt">boolean</span> <span class="n">keepRunning</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span> <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="n">String</span><span class="o">...</span> <span class="n">args</span><span class="o">)</span> <span class="kd">throws</span> <span class="n">Exception</span> <span class="o">{</span> <span class="n">VisibilityTest</span> <span class="n">thread</span> <span class="o">=</span> <span class="k">new</span> <span class="n">VisibilityTest</span><span class="o">();</span> <span class="n">thread</span><span class="o">.</span><span class="na">start</span><span class="o">();</span> <span class="n">Thread</span><span class="o">.</span><span class="na">sleep</span><span class="o">(</span><span class="mi">1000</span><span class="o">);</span> <span class="n">thread</span><span class="o">.</span><span class="na">keepRunning</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span> <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">System</span><span class="o">.</span><span class="na">currentTimeMillis</span><span class="o">()</span> <span class="o">+</span> <span class="s">": keepRunning false"</span><span class="o">);</span> <span class="o">}</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">run</span><span class="o">()</span> <span class="o">{</span> <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">System</span><span class="o">.</span><span class="na">currentTimeMillis</span><span class="o">()</span> <span class="o">+</span> <span class="s">": start"</span><span class="o">);</span> <span class="k">while</span> <span class="o">(</span><span class="n">keepRunning</span><span class="o">);</span> <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">System</span><span class="o">.</span><span class="na">currentTimeMillis</span><span class="o">()</span> <span class="o">+</span> <span class="s">": end"</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span></code></pre></figure> <p>Pour le compiler et l’exécuter :</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash">javac VisibilityTest.java <span class="o">&amp;&amp;</span> java VisibilityTest</code></pre></figure> <p>Sans synchronisation, il est très fort probable que le <em>thread</em> démarré ne se termine jamais, voyant toujours <code class="highlighter-rouge">keepRunning</code> à <code class="highlighter-rouge">true</code>, même si le thread principal lui a donné la valeur <code class="highlighter-rouge">false</code>.</p> <p>Là encore, c’est une optimisation (la mise en cache d’une variable) qui provoque ce comportement “inattendu” sans <a href="http://www.javamex.com/tutorials/synchronization_concurrency_synchronized2.shtml">synchronisation</a>.</p> <p><em>Déclarer <code class="highlighter-rouge">keepRunning</code> <a href="http://www.javamex.com/tutorials/synchronization_volatile.shtml"><code class="highlighter-rouge">volatile</code></a> suffit à résoudre le problème.</em></p> <h2 id="conclusion">Conclusion</h2> <p>La notion de <em>comportement indéfini</em> est très importante pour améliorer la performance des programmes. Mais elle est source de bugs parfois difficiles à</p> <p><code class="highlighter-rouge">Erreur de segmentation</code></p> AImageView (composant Android) 2014-10-20T18:48:34+02:00 http://blog.rom1v.com/2014/10/aimageview-composant-android <p>Pour afficher une image sur <em>Android</em>, le SDK contient un composant <a href="http://developer.android.com/reference/android/widget/ImageView.html"><code class="highlighter-rouge">ImageView</code></a>.</p> <p>Cependant, son mécanisme de configuration du redimensionnement de l’image (<a href="http://developer.android.com/reference/android/widget/ImageView.ScaleType.html"><code class="highlighter-rouge">ScaleType</code></a>) me semble déficient :</p> <ul> <li>il ne gère pas tous les cas courants ;</li> <li>le choix de la bonne constante (si elle existe) n’est pas toujours très intuitif.</li> </ul> <p>J’ai donc écrit un composant <code class="highlighter-rouge">AImageView</code> (qui hérite d’<code class="highlighter-rouge">ImageView</code>) avec un mécanisme alternatif au <em>scale type</em>.</p> <h2 id="principes">Principes</h2> <p><code class="highlighter-rouge">AImageView</code> propose 4 paramètres :</p> <ul> <li><code class="highlighter-rouge">xWeight</code> et <code class="highlighter-rouge">yWeight</code> (des <code class="highlighter-rouge">float</code>s entre <code class="highlighter-rouge">0</code> et <code class="highlighter-rouge">1</code>) indiquent à quel endroit lier l’image au conteneur ;</li> <li><code class="highlighter-rouge">fit</code> indique si l’image doit s’adapter à l’<em>intérieur</em> du composant (en ajoutant des marges), à l’<em>extérieur</em> du composant (en croppant), toujours <em>horizontalement</em> ou toujours <em>verticalement</em>.</li> <li><code class="highlighter-rouge">scale</code> indique si l’on accepte de <em>downscaler</em> (réduire) et/ou d’<em>upscaler</em> (agrandir) l’image ;</li> </ul> <p>Actuellement, il préserve toujours le <a href="https://fr.wikipedia.org/wiki/Format_d%27image">format d’image</a> (aspect ratio).</p> <h2 id="exemple-dutilisation">Exemple d’utilisation</h2> <figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt">&lt;com.rom1v.aimageview.AImageView</span> <span class="na">android:layout_width=</span><span class="s">"match_parent"</span> <span class="na">android:layout_height=</span><span class="s">"match_parent"</span> <span class="na">android:src=</span><span class="s">"@drawable/myimage"</span> <span class="na">app:xWeight=</span><span class="s">"0.5"</span> <span class="na">app:yWeight=</span><span class="s">"0.5"</span> <span class="na">app:fit=</span><span class="s">"inside"</span> <span class="na">app:scale=</span><span class="s">"downscale|upscale"</span> <span class="nt">/&gt;</span></code></pre></figure> <p>Ici, l’image va s’adapter à l’intérieur (<code class="highlighter-rouge">inside</code>) du composant (des marges seront ajoutées si nécessaires), exactement (l’image peut être réduite – <code class="highlighter-rouge">downscale</code> – ou agrandie – <code class="highlighter-rouge">upscale</code> – pour s’adapter) et sera centrée (<code class="highlighter-rouge">0.5</code>, <code class="highlighter-rouge">0.5</code>).</p> <h2 id="quivalences-des-scale-types">Équivalences des <em>scale types</em></h2> <p>Les constantes de <a href="http://developer.android.com/reference/android/widget/ImageView.ScaleType.html"><code class="highlighter-rouge">ScaleType</code></a> du composant standard <code class="highlighter-rouge">ImageView</code> correspondent en fait à des valeurs particulières de ces paramètres. Comme vous pourrez le constater, elles ne couvrent pas toutes les combinaisons, et ne sont pas toujours explicites…</p> <h3 id="scaletypecenter"><code class="highlighter-rouge">ScaleType.CENTER</code></h3> <figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt">&lt;com.rom1v.aimageview.AImageView</span> <span class="na">app:xWeight=</span><span class="s">"0.5"</span> <span class="na">app:yWeight=</span><span class="s">"0.5"</span> <span class="na">app:scale=</span><span class="s">"disabled"</span> <span class="nt">/&gt;</span> <span class="c">&lt;!-- app:fit ne fait rien quand scale vaut "disabled" --&gt;</span></code></pre></figure> <h3 id="scaletypecentercrop"><code class="highlighter-rouge">ScaleType.CENTER_CROP</code></h3> <figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt">&lt;com.rom1v.aimageview.AImageView</span> <span class="na">app:xWeight=</span><span class="s">"0.5"</span> <span class="na">app:yWeight=</span><span class="s">"0.5"</span> <span class="na">app:fit=</span><span class="s">"outside"</span> <span class="na">app:scale=</span><span class="s">"downscale|upscale"</span> <span class="nt">/&gt;</span></code></pre></figure> <h3 id="scaletypecenterinside"><code class="highlighter-rouge">ScaleType.CENTER_INSIDE</code></h3> <figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt">&lt;com.rom1v.aimageview.AImageView</span> <span class="na">app:xWeight=</span><span class="s">"0.5"</span> <span class="na">app:yWeight=</span><span class="s">"0.5"</span> <span class="na">app:fit=</span><span class="s">"inside"</span> <span class="na">app:scale=</span><span class="s">"downscale"</span> <span class="nt">/&gt;</span></code></pre></figure> <h3 id="scaletypefitcenter"><code class="highlighter-rouge">ScaleType.FIT_CENTER</code></h3> <figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt">&lt;com.rom1v.aimageview.AImageView</span> <span class="na">app:xWeight=</span><span class="s">"0.5"</span> <span class="na">app:yWeight=</span><span class="s">"0.5"</span> <span class="na">app:fit=</span><span class="s">"inside"</span> <span class="na">app:scale=</span><span class="s">"downscale|upscale"</span> <span class="nt">/&gt;</span></code></pre></figure> <h3 id="scaletypefitend"><code class="highlighter-rouge">ScaleType.FIT_END</code></h3> <figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt">&lt;com.rom1v.aimageview.AImageView</span> <span class="na">app:xWeight=</span><span class="s">"1"</span> <span class="na">app:yWeight=</span><span class="s">"1"</span> <span class="na">app:fit=</span><span class="s">"inside"</span> <span class="na">app:scale=</span><span class="s">"downscale|upscale"</span> <span class="nt">/&gt;</span></code></pre></figure> <h3 id="scaletypefitstart"><code class="highlighter-rouge">ScaleType.FIT_START</code></h3> <figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt">&lt;com.rom1v.aimageview.AImageView</span> <span class="na">app:xWeight=</span><span class="s">"0"</span> <span class="na">app:yWeight=</span><span class="s">"0"</span> <span class="na">app:fit=</span><span class="s">"inside"</span> <span class="na">app:scale=</span><span class="s">"downscale|upscale"</span> <span class="nt">/&gt;</span></code></pre></figure> <h3 id="scaletypefitxy"><code class="highlighter-rouge">ScaleType.FIT_XY</code></h3> <p>Cette configuration ne peut pas être reproduite en utilisant les paramètres d’<code class="highlighter-rouge">AImageView</code>, car ce composant préserve toujours l’aspect ratio.</p> <h3 id="scaletypematrix"><code class="highlighter-rouge">ScaleType.MATRIX</code></h3> <p><code class="highlighter-rouge">AImageView</code> hérite d’<code class="highlighter-rouge">ImageView</code> et force le <code class="highlighter-rouge">scaleType</code> à <code class="highlighter-rouge">ScaleType.MATRIX</code> (pour redimensionner et déplacer l’image). Par conséquent, il n’y a pas d’équivalent, <code class="highlighter-rouge">AImageView</code> est basé dessus.</p> <h2 id="composant">Composant</h2> <p>Le composant est disponible sous la forme d’un <em>project library</em> (sous licence <del><a href="https://fr.wikipedia.org/wiki/Licence_publique_g%C3%A9n%C3%A9rale_limit%C3%A9e_GNU">GNU/LGPLv3</a></del> (<a href="https://github.com/rom1v/AImageView/commit/436d3085c0219495899616089918b1ddf2063307">plus maintenant</a>) <a href="https://fr.wikipedia.org/wiki/Licence_MIT">MIT</a>):</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash">git clone http://git.rom1v.com/AImageView.git</code></pre></figure> <p>(miroir sur <a href="https://github.com/rom1v/AImageView.git">github</a>)</p> <p>Vous pouvez le <a href="https://github.com/rom1v/AImageView#build">compiler</a> en fichier <code class="highlighter-rouge">.aar</code> grâce à la commande :</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nb">cd </span>AImageView ./gradlew assembleRelease</code></pre></figure> <p>Il sera généré dans <code class="highlighter-rouge">library/build/outputs/aar/aimageview.aar</code>.</p> <p>J’ai aussi écrit une application de démo l’utilisant (avec tous les fichiers <a href="https://en.wikipedia.org/wiki/Gradle">Gradle</a> qui-vont-bien) :</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash">git clone --recursive http://git.rom1v.com/AImageViewSample.git</code></pre></figure> <p>(miroir sur <a href="https://github.com/rom1v/AImageViewSample.git">github</a>)</p> <p class="center"><img src="/assets/aimageview/AImageViewSample.jpg" alt="AImageViewSample" /></p> <p>Pour compiler un <code class="highlighter-rouge">apk</code> de debug (par exemple) :</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nb">cd </span>AImageViewSample ./gradlew assembleDebug</code></pre></figure> <p>Pour ceux que le code intéresse, la classe principale est <a href="https://github.com/rom1v/AImageView/blob/master/library/src/main/java/com/rom1v/aimageview/AImageView.java"><code class="highlighter-rouge">AImageView</code></a>. Pour l’utiliser, la partie importante est dans <a href="https://github.com/rom1v/AImageViewSample/blob/master/app/src/main/res/layout/activity_main.xml"><code class="highlighter-rouge">activity_main.xml</code></a>.</p> <p>N’hésitez pas à critiquer ou à remonter des bugs.</p>