®om's blog 2017-02-16T13:24:27+01:00 http://blog.rom1v.com Romain Vimont rom@rom1v.com 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> <div 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">&quot;my name&quot;</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">&quot;my name&quot;</span><span class="p">);</span> <span class="p">}</span></code></pre></div> <p>ni ceci :</p> <div 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">&quot;my name&quot;</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">&quot;my name&quot;</span><span class="p">);</span> <span class="p">}</span></code></pre></div> <p>À la place, vous écririez sûrement :</p> <div 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">&quot;my name&quot;</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">&quot;my name&quot;</span><span class="p">;</span> <span class="p">}</span></code></pre></div> <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> <div 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></div> <p>Ici, nous avons la responsabilité de supprimer <code>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> <div 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">&quot;c++&quot;</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></div> <p>Ici, le pointeur permet juste de passer l’adresse de l’objet, mais la méthode <code>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>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> <div 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">&quot;file.txt&quot;</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></div> <p>Ici, si l’ouverture du fichier a échoué, <code>parser</code> ne sera jamais libéré.</p> <p>L’exemple suivant est encore plus significatif :</p> <div 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></div> <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>device</code> fait partie de la liste <code>devices</code>, ce code le supprime deux fois :</p> <div 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="c1">// device is deleted twice</span></code></pre></div> <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>DeviceMonitor</code> surveille le branchement de périphériques, et crée pour chacun un objet <code>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>DeviceMonitor::onDeviceLeft(Device *)</code>. Nous voulons alors signaler au reste de l’application que le device est parti (<em>signal</em> <code>DeviceMonitor::deviceLeft(Device *)</code>), puis supprimer l’object <code>device</code> correspondant :</p> <div 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></div> <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>DeviceMonitor::deviceLeft(Device *)</code> en <a href="http://doc.qt.io/qt-5/qt.html#ConnectionType-enum"><code>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>delete</code>, <em>“il faut appeller <a href="http://doc.qt.io/qt-5/qobject.html#deleteLater"><code>deleteLater()</code></a> pour corriger le problème”</em> :</p> <div 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></div> <p>Mais malheureusement, ici, c’est faux : si le <em>slot</em> branché au <em>signal</em> <code>DeviceMonitor::deviceLeft(Device *)</code> est associé à un <a href="http://doc.qt.io/qt-5/qobject.html"><code>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> <div 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="c1">// ?</span></code></pre></div> <p><em>Qt fournit un mécanisme pour supprimer automatiquement les <code>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> <div 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></div> <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> <div 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></div> <p>Évitez :</p> <div 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></div> <p>Préférez :</p> <div 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></div> <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>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>Result</code> ni <code>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>-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> <div 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></div> <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>std::mutex</code></a> en STL ou <a href="http://doc.qt.io/qt-5/qmutex.html"><code>QMutex</code></a> en Qt).</p> <p>Supposons que <code>validate()</code> et <code>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> <div 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="n">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></div> <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>std::lock_guard</code></a> en STL ou <a href="http://doc.qt.io/qt-5/qmutexlocker.html"><code>QMutexLocker</code></a> en Qt), c’est beaucoup plus simple :</p> <div 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></div> <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>QMutexLocker</code></a> :</p> <div 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></div> <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>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>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>std::unique_ptr</code></a></li> <li><a href="http://en.cppreference.com/w/cpp/memory/shared_ptr"><code>std::shared_ptr</code></a></li> <li><a href="http://en.cppreference.com/w/cpp/memory/weak_ptr"><code>std::weak_ptr</code></a></li> <li><a href="http://en.cppreference.com/w/cpp/memory/auto_ptr"><code>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>QSharedPointer</code></a> (équivalent de <code>std::shared_ptr</code>)</li> <li><a href="http://doc.qt.io/qt-5/qweakpointer.html"><code>QWeakPointer</code></a> (équivalent de <code>std::weak_ptr</code>)</li> <li><a href="http://doc.qt.io/qt-5/qscopedpointer.html"><code>QScopedPointer</code></a> (ersatz de <code>std::unique_ptr</code>)</li> <li><a href="http://doc.qt.io/qt-5/qscopedarraypointer.html"><code>QScopedArrayPointer</code></a></li> <li><a href="http://doc.qt.io/qt-5/qpointer.html"><code>QPointer</code></a></li> <li><a href="http://doc.qt.io/qt-5/qshareddatapointer.html"><code>QSharedDataPointer</code></a></li> <li><a href="http://doc.qt.io/qt-5/qexplicitlyshareddatapointer.html"><code>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>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>QScopedPointer</code></a> :</p> <div 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></div> <p>Et un exemple d’utilisation :</p> <div 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></div> <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>Device</code>s <em>partagés</em> par des <a href="http://doc.qt.io/qt-5/qsharedpointer.html"><code>QSharedPointer</code></a>s :</p> <div 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="nf">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="nf">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="c1">// devices are automatically deleted when necessary</span></code></pre></div> <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>getDevice(…)</code> et <code>add(…)</code> manipulent un <a href="http://doc.qt.io/qt-5/qsharedpointer.html"><code>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>add</code> et <code>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>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> <div 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></div> <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>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> <div 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="nf">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="nf">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></div> <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>Person</code> suivante (<code>person.h</code>) :</p> <div 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="nf">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></div> <p>Elle contient juste un <em>nom</em> et un <em>âge</em>. Elle définit par ailleurs une méthode privée, <code>countYears(…)</code>, qu’on imagine appelée dans <code>getAge()</code>.</p> <p>Chaque classe désirant utiliser la classe <code>Person</code> devra l’inclure :</p> <div class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="cp">#include &quot;person.h&quot;</span></code></pre></div> <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>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>Person</code> précédente est la partie privée. Renommons-la :</p> <div 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="nf">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></div> <p>Créons la partie publique, définissant l’interface souhaitée :</p> <div 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="nf">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></div> <p>Elle contient un pointeur vers <code>PersonPrivate</code>, et lui délègue tous les appels.</p> <p>Évidemment, <code>Person</code> ne doit pas inclure <code>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> <div 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></div> <p>Le pointeur vers la classe privée est souvent nommé <code>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>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> <div 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">&quot;ABC&quot;</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">&quot;DEF&quot;</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">&quot;ABCDEF&quot;</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">&quot;ABC&quot;</span><span class="p">);</span></code></pre></div> <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> <div 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">&quot;ABC&quot;</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">&quot;DEF&quot;</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">&quot;DEF&quot;</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">&quot;DEF&quot;</span><span class="p">);</span></code></pre></div> <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> <div 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></div> <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> <div 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></div> <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>std::unique_ptr</code></a></li> <li><a href="http://doc.qt.io/qt-5/qscopedpointer.html"><code>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>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>std::shared_ptr</code></a></li> <li><a href="http://doc.qt.io/qt-5/qsharedpointer.html"><code>QSharedPointer</code></a></li> </ul> <p>Par exemple, donnons à notre classe <code>Person</code> une sémantique d’entité :</p> <div 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 &quot;null&quot; 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></div> <p><code>Person</code> se comporte maintenant <em>comme un pointeur</em>.</p> <div class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="n">Person</span> <span class="nf">p1</span><span class="p">(</span><span class="s">&quot;ABC&quot;</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">&quot;DEF&quot;</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">&quot;DEF&quot;</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">&quot;DEF&quot;</span><span class="p">);</span></code></pre></div> <p><code>p1</code> et <code>p2</code> sont alors des <em>resource handles</em> vers <code>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>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>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>UsbDevice</code> et <code>UsbDeviceHandle</code> qui wrappent les structures C <a href="http://libusb.sourceforge.net/api-1.0/group__dev.html#ga77eedd00d01eb7569b880e861a971c2b"><code>libusb_device</code></a> et <a href="http://libusb.sourceforge.net/api-1.0/group__dev.html#ga7df95821d20d27b5597f1d783749d6a4"><code>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> <div 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="nl">signals</span><span class="p">:</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="nf">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></div> <p><code>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>QString</code></a>).</p> <div 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="nl">device</span> <span class="p">:</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></div> <p>Si une souris est trouvée dans la liste, on la retourne simplement ; sinon, on retourne un <code>UsbDevice</code> “<a href="http://doc.qt.io/qt-5/qstring.html#isNull"><em>null</em></a>”.</p> <div 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="nf">deviceArrived</span><span class="p">(</span><span class="n">device</span><span class="p">);</span> <span class="p">}</span></code></pre></div> <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> <div 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></div> <div 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></div> <p><code>UsbDevice</code> peut naviguer entre la couche C++ et QML.</p> <div 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></div> <p>Grâce à RAII, les connexions (<code>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>new</code> et <code>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>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>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>QObject</code>s are <em>identities</em>, not <em>values</em>.</p> </blockquote> <p>Très concrètement, cela implique que <code>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>QString</code></a> et <a href="http://doc.qt.io/qt-5/qlist.html"><code>QList</code></a> n’héritent pas de <a href="http://doc.qt.io/qt-5/qobject.html"><code>QObject</code></a>.</p> <h2 id="rsum">Résumé</h2> <div 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">&quot;just use values&quot;</span><span class="p">;</span> <span class="k">return</span> <span class="s">&quot;use PImpl + QSharedDataPointer&quot;</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">&quot;use PImpl + QSharedPointer&quot;</span><span class="p">;</span> <span class="k">return</span> <span class="s">&quot;use smart pointers explicitly&quot;</span><span class="p">;</span> <span class="p">};</span></code></pre></div> <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>new</code> et <code>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>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> <pre><code>jekyll build </code></pre> <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>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>_comments</code>) parsé par un script à insérer dans le répertoire <code>_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>_posts</code> une liste de commentaires dans <code>_data</code> (un répertoire géré nativement par <em>Jekyll</em>).</p> <p>Par exemple, ce billet est stocké dans :</p> <pre><code>_posts/2017-01-09-commentaires-statiques-avec-jekyll.md </code></pre> <p>Dans l’idéal, je voudrais que les commentaires associés soient stockés dans :</p> <pre><code>_data/comments-2017-01-09-commentaires-statiques-avec-jekyll.yaml </code></pre> <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> <pre><code>_data/comments-2017-01-commentaires-statiques-avec-jekyll.yaml </code></pre> <p>Il suffit alors de stocker dans ces fichiers les commentaires sous cette forme :</p> <div class="highlight"><pre><code class="language-yaml" data-lang="yaml"><span class="p-Indicator">-</span> <span class="l-Scalar-Plain">id</span><span class="p-Indicator">:</span> <span class="l-Scalar-Plain">1</span> <span class="l-Scalar-Plain">author</span><span class="p-Indicator">:</span> <span class="l-Scalar-Plain">this_is_me</span> <span class="l-Scalar-Plain">date</span><span class="p-Indicator">:</span> <span class="l-Scalar-Plain">2017-01-02 10:11:12+01:00</span> <span class="l-Scalar-Plain">contents</span><span class="p-Indicator">:</span> <span class="p-Indicator">|</span> <span class="no">Bonjour,</span> <span class="no">Ceci est un commentaire écrit en _markdown_.</span> <span class="p-Indicator">-</span> <span class="l-Scalar-Plain">id</span><span class="p-Indicator">:</span> <span class="l-Scalar-Plain">2</span> <span class="l-Scalar-Plain">author</span><span class="p-Indicator">:</span> <span class="l-Scalar-Plain">dev42</span> <span class="l-Scalar-Plain">author-url</span><span class="p-Indicator">:</span> <span class="l-Scalar-Plain">https://github.com</span> <span class="l-Scalar-Plain">date</span><span class="p-Indicator">:</span> <span class="l-Scalar-Plain">2017-01-02 12:11:10+01:00</span> <span class="l-Scalar-Plain">contents</span><span class="p-Indicator">:</span> <span class="p-Indicator">|</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></div> <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>page.id</code>, qui ici vaut :</p> <pre><code>/2017/01/commentaires-statiques-avec-jekyll </code></pre> <p>Cette ligne de <em>Liquid</em> :</p> <div 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">&#39;/&#39;</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;-&#39;</span><span class="w"> </span><span class="p">}}</span></code></pre></div> <p>donne la valeur :</p> <pre><code>comments-2017-01-commentaires-statiques-avec-jekyll </code></pre> <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>_include/comments.html</code></a>) :</p> <div class="highlight"><pre><code class="language-html" data-lang="html">{% capture commentid %}comments{{ page.id | replace: &#39;/&#39;, &#39;-&#39; }}{% endcapture %} {% if site.data[commentid] %} <span class="nt">&lt;h2</span> <span class="na">id=</span><span class="s">&quot;comments&quot;</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">&quot;comments&quot;</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">&quot;comment-{{ comment.id }}&quot;</span> <span class="na">class=</span><span class="s">&quot;comment&quot;</span> <span class="nt">/&gt;</span> <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">&quot;comment-author&quot;</span><span class="nt">&gt;</span> {% if (comment.author-url) %} <span class="nt">&lt;a</span> <span class="na">href=</span><span class="s">&quot;{{comment.author-url}}&quot;</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">&quot;comment-date&quot;</span><span class="nt">&gt;</span> <span class="nt">&lt;a</span> <span class="na">href=</span><span class="s">&quot;#comment-{{ comment.id }}&quot;</span><span class="nt">&gt;</span> {{ comment.date | date: &quot;%-d %B %Y, %H:%M&quot; }} <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">&quot;comment-contents&quot;</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></div> <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>_layout/post.html</code></a>) :</p> <div 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>comments.html<span class="w"> </span><span class="p">%}</span></code></pre></div> <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>_include/comments.html</code></a>) :</p> <div class="highlight"><pre><code class="language-html" data-lang="html"><span class="nt">&lt;h3</span> <span class="na">class=</span><span class="s">&quot;comment-title&quot;</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">&quot;POST&quot;</span> <span class="na">action=</span><span class="s">&quot;/comments/submit.php&quot;</span><span class="nt">&gt;</span> <span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">&quot;hidden&quot;</span> <span class="na">name=</span><span class="s">&quot;post_id&quot;</span> <span class="na">value=</span><span class="s">&quot;{{ page.id }}&quot;</span> <span class="nt">/&gt;</span> <span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">&quot;hidden&quot;</span> <span class="na">name=</span><span class="s">&quot;return_url&quot;</span> <span class="na">value=</span><span class="s">&quot;{{ page.url }}&quot;</span> <span class="nt">/&gt;</span> <span class="nt">&lt;table</span> <span class="na">class=</span><span class="s">&quot;comment-table&quot;</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">&quot;text&quot;</span> <span class="na">size=</span><span class="s">&quot;25&quot;</span> <span class="na">name=</span><span class="s">&quot;name&quot;</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">&quot;text&quot;</span> <span class="na">size=</span><span class="s">&quot;25&quot;</span> <span class="na">name=</span><span class="s">&quot;email&quot;</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">&quot;text&quot;</span> <span class="na">size=</span><span class="s">&quot;25&quot;</span> <span class="na">name=</span><span class="s">&quot;url&quot;</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">&quot;2&quot;</span><span class="nt">&gt;</span> <span class="nt">&lt;textarea</span> <span class="na">name=</span><span class="s">&quot;comment&quot;</span> <span class="na">rows=</span><span class="s">&quot;10&quot;</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">&quot;2&quot;</span><span class="nt">&gt;</span> <span class="nt">&lt;input</span> <span class="na">class=</span><span class="s">&quot;comment-submit&quot;</span> <span class="na">type=</span><span class="s">&quot;submit&quot;</span> <span class="na">value=</span><span class="s">&quot;Envoyer&quot;</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></div> <p>Ce formulaire est affiché sous les commentaires existants.</p> <h2 id="traitement">Traitement</h2> <p>L’<code>action</code> du formulaire précédent pointait sur <a href="https://github.com/rom1v/blog.rom1v.com/blob/master/comments/submit.php"><code>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>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> <div 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">&quot;Y-m-d H:i:sP&quot;</span><span class="p">;</span> <span class="nv">$EMAIL_ADDRESS</span> <span class="o">=</span> <span class="s2">&quot;your@email&quot;</span><span class="p">;</span> <span class="nv">$SUBJECT</span> <span class="o">=</span> <span class="s2">&quot;Nouveau commentaire&quot;</span><span class="p">;</span> <span class="nv">$COMMENT_SENT</span> <span class="o">=</span> <span class="s2">&quot;sent.html&quot;</span><span class="p">;</span> <span class="nv">$msg</span> <span class="o">=</span> <span class="s2">&quot;post_id: &quot;</span> <span class="o">.</span> <span class="nv">$_POST</span><span class="p">[</span><span class="s2">&quot;post_id&quot;</span><span class="p">]</span> <span class="o">.</span> <span class="s2">&quot;</span><span class="se">\n</span><span class="s2">&quot;</span><span class="p">;</span> <span class="nv">$msg</span> <span class="o">.=</span> <span class="s2">&quot;email: &quot;</span> <span class="o">.</span> <span class="nv">$_POST</span><span class="p">[</span><span class="s2">&quot;email&quot;</span><span class="p">]</span> <span class="o">.</span> <span class="s2">&quot;</span><span class="se">\n</span><span class="s2">&quot;</span><span class="p">;</span> <span class="nv">$msg</span> <span class="o">.=</span> <span class="s2">&quot;---</span><span class="se">\n</span><span class="s2">&quot;</span><span class="p">;</span> <span class="nv">$msg</span> <span class="o">.=</span> <span class="s2">&quot;- id: ?</span><span class="se">\n</span><span class="s2">&quot;</span><span class="p">;</span> <span class="nv">$msg</span> <span class="o">.=</span> <span class="s2">&quot; author: &quot;</span> <span class="o">.</span> <span class="nv">$_POST</span><span class="p">[</span><span class="s2">&quot;name&quot;</span><span class="p">]</span> <span class="o">.</span> <span class="s2">&quot;</span><span class="se">\n</span><span class="s2">&quot;</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">&quot;url&quot;</span><span class="p">]</span> <span class="o">!==</span> <span class="s1">&#39;&#39;</span><span class="p">)</span> <span class="p">{</span> <span class="nv">$msg</span> <span class="o">.=</span> <span class="s2">&quot; author-url: &quot;</span> <span class="o">.</span> <span class="nv">$_POST</span><span class="p">[</span><span class="s2">&quot;url&quot;</span><span class="p">]</span> <span class="o">.</span> <span class="s2">&quot;</span><span class="se">\n</span><span class="s2">&quot;</span><span class="p">;</span> <span class="p">}</span> <span class="nv">$msg</span> <span class="o">.=</span> <span class="s2">&quot; date: &quot;</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">&quot;</span><span class="se">\n</span><span class="s2">&quot;</span><span class="p">;</span> <span class="nv">$msg</span> <span class="o">.=</span> <span class="s2">&quot; contents: |</span><span class="se">\n</span><span class="s2">&quot;</span> <span class="o">.</span> <span class="nv">$_POST</span><span class="p">[</span><span class="s2">&quot;comment&quot;</span><span class="p">];</span> <span class="nv">$headers</span> <span class="o">=</span> <span class="s2">&quot;From: </span><span class="si">$EMAIL_ADDRESS\n</span><span class="s2">&quot;</span><span class="p">;</span> <span class="nv">$headers</span> <span class="o">.=</span> <span class="s2">&quot;Content-Type: text/plain; charset=utf-8&quot;</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">&quot;Le commentaire n&#39;a pas pu être envoyé.&quot;</span><span class="p">;</span> <span class="p">}</span></code></pre></div> <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>comments/sent.html</code></a> est affichée à l’utilisateur.</p> <p>Ainsi, lorsqu’un commentaire est posté, je reçois un mail :</p> <pre><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> <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>.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>_data/comments-*.yaml</code></li> <li><code>_include/comments.html</code></li> <li><code>comments/submit.php</code></li> <li><code>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> <div 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">&quot;Hire me!!!!!!!!&quot;</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></div> <p>Ma preuve de solution :</p> <div class="highlight"><pre><code class="language-diff" data-lang="diff"><span class="gh">diff --git a/HireMe.cpp b/HireMe.cpp</span> <span class="gh">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,</span> <span class="gd">-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,</span> <span class="gi">+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[]=&quot;Hire me!!!!!!!!&quot;;</span> <span class="gi">+ u8 target[]=&quot;[rom@rom1v.com]&quot;;</span> u8 output[32]; Forward(input,output,confusion,diffusion);</code></pre></div> 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> <pre><code>git clone http://git.rom1v.com/metahanoi.git </code></pre> <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> <div class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="cp">#include &lt;iterator&gt;</span> <span class="cp">#include &lt;iostream&gt;</span> <span class="cp">#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></div> <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>runtime</code>)</p> <p>À compiler avec :</p> <div class="highlight"><pre><code class="language-bash" data-lang="bash">g++ -std<span class="o">=</span>c++11 hanoi.cpp -o hanoi</code></pre></div> <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>{ 0, 0, 0, 0 }</code> représente 4 disques sur la tour 0 :</p> <pre><code>state = { 0, 0, 0, 0 }; 0 1 2 | | | -+- | | --+-- | | ---+--- | | ----+---- | | </code></pre> <p>L’état <code>{ 1, 1, 0, 2 }</code>, quant-à lui, représente ces positions:</p> <pre><code>state = { 1, 1, 2, 1 }; 0 1 2 | | | | | | | -+- | | ---+--- | | ----+---- --+-- </code></pre> <p>Dans cette version, pour déplacer le disque <code>i</code>, il suffit alors de changer la valeur de <code>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>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>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>{ 1, 2, 1, 0, 2 }</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> <pre><code>$ echo 'ibase=3; 12102' | bc 146 $ echo 'obase=3; 146' | bc 12102 </code></pre> <p>Nous allons utiliser le type entier le plus long, à savoir <a href="https://fr.wikipedia.org/wiki/C_%28langage%29#Types"><code>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>state</code> :</p> <div 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></div> <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>constexpr</code></a>. Une fonction <code>constexpr</code> doit, en gros, n’être composé que d’une instruction <code>return</code>.</p> <p>C’est le cas à l’évidence pour notre fonction <code>other(…)</code> :</p> <div 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></div> <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>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>{ 0, 1, 2, 0, 0 }</code> (ou plus simplement <code>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>2</code> par un <code>1</code> (rappelez-vous, les disques sont indexés du plus grand au plus petit). Le résultat attendu est donc <code>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> <pre><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> <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>N / b * b + d</code>, soit de manière équivalente <code>N - N % b + d</code> (<code>/</code> représentant la <a href="https://fr.wikipedia.org/wiki/Division_euclidienne">division entière</a> et <code>%</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> <pre><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> <p>Arbitrairement, nous utiliserons la première méthode pour implémenter notre fonction <code>constexpr</code> <code>move(…)</code> :</p> <div 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="nl">target</span> <span class="p">:</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></div> <p>De la même manière, définissons une fonction <code>getTower(s, i)</code> qui retourne la tour sur laquelle se trouve le _i_ème plus petit disque :</p> <div 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></div> <p>Attaquons-nous maintenant à la conversion de la fonction <code>solveRec(…)</code>. Elle contenait deux branchements (<code>if</code>) et plusieurs instructions séquentielles, que nous allons devoir transformer en une seule instruction <code>return</code>.</p> <p>Pour cela, nous allons remplacer les branchements par des opérateurs ternaires :</p> <div 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></div> <p>devient :</p> <div 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="nl">A</span> <span class="p">:</span> <span class="n">B</span><span class="p">;</span></code></pre></div> <p>Notez que contrairement à la version <code>if</code>/<code>else</code>, cela implique que les expressions retournent une valeur. Cela tombe bien, comme une fonction <code>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> <div 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></div> <p>par:</p> <div 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></div> <p>En combinant ces principes, la méthode <code>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> <div 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="nl">s</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="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></div> <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> <div 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></div> <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>constexpr</code>)</p> <p>Compilons et exécutons le programme :</p> <pre><code>$ g++ -std=c++11 metahanoi.cpp &amp;&amp; ./metahanoi 22222 </code></pre> <p>L’état <code>22222</code> (soit l’entier <em>242</em>) est bien écrit en dur dans le binaire généré :</p> <pre><code>$ g++ -std=c++11 -S metahanoi.cpp &amp;&amp; grep 242 metahanoi.s movq $242, -16(%rbp) movl $242, %edx </code></pre> <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>std::vector&lt;int&gt;</code> définit un nouveau type paramétré par le type <code>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>std::vector&lt;int&gt;</code> et <code>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> <pre><code> +----------------+ | CLASS TEMPLATE | +----------------+ | template | instantiation v COMPILE TIME +----------------+ | CLASS / TYPE | ----------------------- +----------------+ | class RUNTIME | instantiation v +----------------+ | OBJECT | +----------------+ </code></pre> <p>De la même manière qu’une variable d’instance existe pour chaque objet (instance de classe), une variable <code>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>static</code>. Nous voulons donc remplacer notre fonction <code>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> <div 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></div> <p>Ainsi, l’expression :</p> <div 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></div> <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> <div 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></div> <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>Fact&lt;N - 1&gt;</code> sera généré même si <code>N == 0</code>. La récursivité ne s’arrête donc jamais, provoquant une erreur à la compilation :</p> <pre><code>error: template instantiation depth exceeds maximum of 900 </code></pre> <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> <div 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></div> <p>Lorsque <code>Fact</code> est instancié avec le paramètre <code>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>Solver</code> avec 3 paramètres de template, les mêmes que notre fonction <code>solve(…)</code> :</p> <div 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></div> <p>Puis nous allons en définir une spécialisation pour le cas où <code>DISKS</code> vaut 0 :</p> <div 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></div> <p>Nous avons ainsi implémenté le premier branchement sur la condition <code>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>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>SRC</code> afin de pouvoir sélectionner la bonne implémentation en fonction de la condition <code>SRC == TARGET</code>. Sa valeur étant déterminée par celle des autres paramètres, l’ajout de <code>SRC</code> ne va pas entraîner la création de nouveaux types. Par contre, il multiplie les cas à implémenter :</p> <div 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&#39;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&#39;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></div> <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>SRC</code> est ajouté, il va falloir le calculer (grâce à <code>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>getTower(…)</code> avec <code>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>SolverRec</code> à écrire. Chacune devra contenir l’état final résultant du déplacement de <code>DISKS</code> disques de la tour <code>SRC</code> vers la tour <code>TARGET</code> à partir de l’état <code>S</code>. Cet état sera stocké dans une constante <code>final_state</code>, présente dans chacune des spécialisations.</p> <p>Voici mon implémentation :</p> <div 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></div> <p>Le type (déterminé par les arguments des templates) correspondant aux appels récursifs dépend des valeurs <code>constexpr</code> calculées dans la classe. C’est à l’appelant de calculer la tour source pour renseigner la valeur du paramètre <code>SRC</code>.</p> <p>Par commodité, nous pouvons aussi ajouter une <em>classe template</em> <code>Solver</code>, qui calcule elle-même la tour <code>SRC</code> du plus grand disque lors du démarrage.</p> <div 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></div> <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>templates</code>)</p> <p>De cette manière, pour calculer l’état résultant du déplacement de 5 disques à l’état <code>00000</code> (l’entier 0) vers la tour 2, il suffit de lire la variable :</p> <div 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></div> <p>Nous avons donc converti notre implementation d’une simple fonction <code>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>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> <div 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></div> <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> <div 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></div> <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> <div 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></div> <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> <div 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></div> <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>sizeof...</code></a>.</p> <p>L’état compacté est stocké dans la variable <code>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>packed</code>.</p> <p>Nous pouvons maintenant initialiser notre <code>solver</code> ainsi :</p> <div 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></div> <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>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>print(…)</code> dans chacune des classes <code>SolverRec</code>, affichant l’état associé et/ou appellant récursivement les méthodes <code>print(…)</code> sur les instances de <code>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>S</code>) ;</li> <li>l’état après le premier appel récursif (<code>rec1::final_state</code>) ;</li> <li>l’état après le déplacement (<code>value</code>) ;</li> <li>l’état après le second appel récursif (<code>final_state</code>).</li> </ul> <p>C’est en fait l’état <code>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>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>SolverRec</code> une méthode :</p> <div 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></div> <p>Le paramètre <code>std::ostream &amp;os</code> permet juste de préciser sur quel flux écrire (<code>std::cout</code> par exemple) ; il est retourné pour pouvoir le chaîner avec d’autres écritures (comme <code>&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>DISKS</code>, qui correspond au nombre de disques à déplacer pour le niveau de récursivité courant.</p> <div 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></div> <p>Seules les versions du template pour lesquelles <code>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>Solver</code> (sans le paramètre <code>ndisks</code>, car ici il est toujours égal à <code>DISKS</code>) :</p> <div 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></div> <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>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> <div 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></div> <p>Le défi est maintenant d’initialiser les champs <code>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>value</code>) associé à une instance du template <code>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>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>first</code>) et passer à chaque classe appelée le nœud qui devra suivre son nœud final (<code>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> <div 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="k">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></div> <p>La variable <code>static</code> <code>node</code> n’étant pas <code>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>ResultNode</code> itérable (j’implémente ici uniquement le strict minimum pour que l’<em>iterator</em> fonctionne) :</p> <div 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="nf">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="k">nullptr</span><span class="p">);</span> <span class="p">}</span> <span class="p">};</span></code></pre></div> <p>La liste peut être parcourue ainsi :</p> <div 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="nl">s</span> <span class="p">:</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></div> <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>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> <pre><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> <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>value</code> et les deux suivants le champ <code>next</code> de <code>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>undefined.c</code>) :</p> <table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><code class="language-c" data-lang="c"> 1 2 3 4 5 6 7 8 9 10 11</code></pre></div></td><td class="code"><div class="highlight"><pre><span class="cp">#include &lt;stdio.h&gt;</span> <span class="cp">#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">&quot;pwnd %d</span><span class="se">\n</span><span class="s">&quot;</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> </pre></div> </td></tr></table> <p>Si <code>argc</code> vaut <code>1</code> (c’est-à-dire si nous appelons l’exécutable sans passer d’arguments de la ligne de commande), alors le pointeur <code>i</code> est <code>NULL</code> (ligne 5).</p> <p>Cette ligne peut paraître étrange, mais elle permet de faire dépendre <code>i</code> d’une valeur connue uniquement à l’exécution (<code>argc</code>), ce qui évite au compilateur de savoir à l’avance que <code>i</code> est <code>NULL</code>.</p> <p>La ligne 6 (<code>*i = 42</code>) est donc incorrecte : nous n’avons pas le droit de déréférencer un pointeur <code>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>i</code> est <code>NULL</code>, la fonction se termine (en retournant <code>1</code>, ligne 8).</p> <p>Donc il n’y a donc aucun moyen d’afficher le contenu du <code>printf</code> ligne 9.</p> <p>Et bien… en fait, si !</p> <h2 id="excution">Exécution</h2> <p>Essayons (j’utilise <code>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>gcc</code>) :</p> <pre><code>$ gcc -Wall undefined.c $ ./a.out # argc == 1 Erreur de segmentation $ ./a.out hello # argc == 2 pwnd 42 </code></pre> <p>Jusqu’ici, tout va bien. Maintenant, activons des optimisations de compilation :</p> <pre><code>$ gcc -Wall -O2 undefined.c $ ./a.out # argc == 1 pwnd 42 </code></pre> <p>Voilà, nous avons réussi à exécuter le <code>printf</code> alors que <code>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>undefined.s</code>) :</p> <div class="highlight"><pre><code class="language-bash" data-lang="bash">gcc -S undefined.c</code></pre></div> <p>J’ai commenté les parties importantes :</p> <table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><code class="language-nasm" data-lang="nasm"> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50</code></pre></div></td><td class="code"><div class="highlight"><pre><span class="nf">.file</span> <span class="s">&quot;undefined.c&quot;</span> <span class="nf">.section</span> <span class="nv">.rodata</span> <span class="nl">.LC0:</span> <span class="nf">.string</span> <span class="s">&quot;pwnd %d\n&quot;</span> <span class="nf">.text</span> <span class="nf">.globl</span> <span class="nv">main</span> <span class="nf">.type</span> <span class="nv">main</span><span class="p">,</span> <span class="err">@</span><span class="nv">function</span> <span class="nl">main:</span> <span class="nl">.LFB0:</span> <span class="nf">.cfi_startproc</span> <span class="nf">pushq</span> <span class="o">%</span><span class="nb">rbp</span> <span class="nf">.cfi_def_cfa_offset</span> <span class="mi">16</span> <span class="nf">.cfi_offset</span> <span class="mi">6</span><span class="p">,</span> <span class="o">-</span><span class="mi">16</span> <span class="nf">movq</span> <span class="o">%</span><span class="nb">rsp</span><span class="p">,</span> <span class="o">%</span><span class="nb">rbp</span> <span class="nf">.cfi_def_cfa_register</span> <span class="mi">6</span> <span class="nf">subq</span> <span class="kc">$</span><span class="mi">32</span><span class="p">,</span> <span class="o">%</span><span class="nb">rsp</span> <span class="nf">movl</span> <span class="o">%</span><span class="nb">edi</span><span class="p">,</span> <span class="o">-</span><span class="mi">20</span><span class="p">(</span><span class="o">%</span><span class="nb">rbp</span><span class="p">)</span> <span class="nf">movq</span> <span class="o">%</span><span class="nb">rsi</span><span class="p">,</span> <span class="o">-</span><span class="mi">32</span><span class="p">(</span><span class="o">%</span><span class="nb">rbp</span><span class="p">)</span> <span class="nf">cmpl</span> <span class="kc">$</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="o">%</span><span class="nb">rbp</span><span class="p">)</span> <span class="c1">; if (argc == 1)</span> <span class="nf">je</span> <span class="nv">.L2</span> <span class="c1">; goto .L2</span> <span class="nf">movl</span> <span class="kc">$</span><span class="mi">4</span><span class="p">,</span> <span class="o">%</span><span class="nb">edi</span> <span class="c1">; arg0 = 4 // sizeof(int)</span> <span class="nf">call</span> <span class="nv">malloc</span> <span class="c1">; tmp = malloc(4)</span> <span class="nf">jmp</span> <span class="nv">.L3</span> <span class="c1">; goto .L3</span> <span class="nl">.L2:</span> <span class="nf">movl</span> <span class="kc">$</span><span class="mi">0</span><span class="p">,</span> <span class="o">%</span><span class="nb">eax</span> <span class="nl">.L3:</span> <span class="nf">movq</span> <span class="o">%</span><span class="nb">rax</span><span class="p">,</span> <span class="o">-</span><span class="mi">8</span><span class="p">(</span><span class="o">%</span><span class="nb">rbp</span><span class="p">)</span> <span class="c1">; i = tmp</span> <span class="nf">movq</span> <span class="o">-</span><span class="mi">8</span><span class="p">(</span><span class="o">%</span><span class="nb">rbp</span><span class="p">),</span> <span class="o">%</span><span class="nb">rax</span> <span class="nf">movl</span> <span class="kc">$</span><span class="mi">42</span><span class="p">,</span> <span class="p">(</span><span class="o">%</span><span class="nb">rax</span><span class="p">)</span> <span class="c1">; *i = 42</span> <span class="nf">cmpq</span> <span class="kc">$</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="o">%</span><span class="nb">rbp</span><span class="p">)</span> <span class="c1">; if (!i)</span> <span class="nf">jne</span> <span class="nv">.L4</span> <span class="c1">; goto .L4</span> <span class="nf">movl</span> <span class="kc">$</span><span class="mi">1</span><span class="p">,</span> <span class="o">%</span><span class="nb">eax</span> <span class="c1">; ret = 1</span> <span class="nf">jmp</span> <span class="nv">.L5</span> <span class="nl">.L4:</span> <span class="nf">movq</span> <span class="o">-</span><span class="mi">8</span><span class="p">(</span><span class="o">%</span><span class="nb">rbp</span><span class="p">),</span> <span class="o">%</span><span class="nb">rax</span> <span class="nf">movl</span> <span class="p">(</span><span class="o">%</span><span class="nb">rax</span><span class="p">),</span> <span class="o">%</span><span class="nb">eax</span> <span class="nf">movl</span> <span class="o">%</span><span class="nb">eax</span><span class="p">,</span> <span class="o">%</span><span class="nb">esi</span> <span class="c1">; arg1 = *i</span> <span class="nf">movl</span> <span class="kc">$</span><span class="nv">.LC0</span><span class="p">,</span> <span class="o">%</span><span class="nb">edi</span> <span class="c1">; arg0 points to &quot;pwnd %d\n&quot;</span> <span class="nf">movl</span> <span class="kc">$</span><span class="mi">0</span><span class="p">,</span> <span class="o">%</span><span class="nb">eax</span> <span class="nf">call</span> <span class="nv">printf</span> <span class="c1">; printf(&quot;pwnd %d\n&quot;, *i)</span> <span class="nf">movl</span> <span class="kc">$</span><span class="mi">0</span><span class="p">,</span> <span class="o">%</span><span class="nb">eax</span> <span class="c1">; ret = 0</span> <span class="nl">.L5:</span> <span class="nf">leave</span> <span class="nf">.cfi_def_cfa</span> <span class="mi">7</span><span class="p">,</span> <span class="mi">8</span> <span class="nf">ret</span> <span class="c1">; return ret</span> <span class="nf">.cfi_endproc</span> <span class="nl">.LFE0:</span> <span class="nf">.size</span> <span class="nv">main</span><span class="p">,</span> <span class="nv">.</span><span class="o">-</span><span class="nv">main</span> <span class="nf">.ident</span> <span class="s">&quot;GCC: (Debian 4.7.2-5) 4.7.2&quot;</span> <span class="nf">.section</span> <span class="nv">.note.GNU</span><span class="o">-</span><span class="nv">stack</span><span class="p">,</span><span class="s">&quot;&quot;</span><span class="p">,</span><span class="err">@</span><span class="nv">progbits</span> </pre></div> </td></tr></table> <p>Le code généré est très fidèle au code source C.</p> <h3 id="avec-gcc--o">Avec <code>gcc -O</code></h3> <p>Maintenant, activons certaines optimisations :</p> <div class="highlight"><pre><code class="language-bash" data-lang="bash">gcc -O -S undefined.c</code></pre></div> <p>Ce qui donne :</p> <table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><code class="language-nasm" data-lang="nasm"> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42</code></pre></div></td><td class="code"><div class="highlight"><pre><span class="nf">.file</span> <span class="s">&quot;undefined.c&quot;</span> <span class="nf">.section</span> <span class="nv">.rodata.str1.1</span><span class="p">,</span><span class="s">&quot;aMS&quot;</span><span class="p">,</span><span class="err">@</span><span class="nv">progbits</span><span class="p">,</span><span class="mi">1</span> <span class="nl">.LC0:</span> <span class="nf">.string</span> <span class="s">&quot;pwnd %d\n&quot;</span> <span class="nf">.text</span> <span class="nf">.globl</span> <span class="nv">main</span> <span class="nf">.type</span> <span class="nv">main</span><span class="p">,</span> <span class="err">@</span><span class="nv">function</span> <span class="nl">main:</span> <span class="nl">.LFB11:</span> <span class="nf">.cfi_startproc</span> <span class="nf">cmpl</span> <span class="kc">$</span><span class="mi">1</span><span class="p">,</span> <span class="o">%</span><span class="nb">edi</span> <span class="c1">; if (argc == 1)</span> <span class="nf">je</span> <span class="nv">.L2</span> <span class="c1">; goto .L2</span> <span class="nf">subq</span> <span class="kc">$</span><span class="mi">8</span><span class="p">,</span> <span class="o">%</span><span class="nb">rsp</span> <span class="nf">.cfi_def_cfa_offset</span> <span class="mi">16</span> <span class="nf">movl</span> <span class="kc">$</span><span class="mi">4</span><span class="p">,</span> <span class="o">%</span><span class="nb">edi</span> <span class="c1">; arg0 = 4 // sizeof(int)</span> <span class="nf">call</span> <span class="nv">malloc</span> <span class="c1">; tmp = malloc(4)</span> <span class="nf">movq</span> <span class="o">%</span><span class="nb">rax</span><span class="p">,</span> <span class="o">%</span><span class="nb">rdx</span> <span class="c1">; i = tmp</span> <span class="nf">movl</span> <span class="kc">$</span><span class="mi">42</span><span class="p">,</span> <span class="p">(</span><span class="o">%</span><span class="nb">rax</span><span class="p">)</span> <span class="c1">; *i = 42</span> <span class="nf">movl</span> <span class="kc">$</span><span class="mi">1</span><span class="p">,</span> <span class="o">%</span><span class="nb">eax</span> <span class="c1">; ret = 1</span> <span class="nf">testq</span> <span class="o">%</span><span class="nb">rdx</span><span class="p">,</span> <span class="o">%</span><span class="nb">rdx</span> <span class="c1">; if (!i)</span> <span class="nf">je</span> <span class="nv">.L5</span> <span class="c1">; goto .L5</span> <span class="nf">movl</span> <span class="kc">$</span><span class="mi">42</span><span class="p">,</span> <span class="o">%</span><span class="nb">esi</span> <span class="c1">; arg1 = 42</span> <span class="nf">movl</span> <span class="kc">$</span><span class="nv">.LC0</span><span class="p">,</span> <span class="o">%</span><span class="nb">edi</span> <span class="c1">; arg0 points to &quot;pwnd %d\n&quot;</span> <span class="nf">movl</span> <span class="kc">$</span><span class="mi">0</span><span class="p">,</span> <span class="o">%</span><span class="nb">eax</span> <span class="nf">call</span> <span class="nv">printf</span> <span class="c1">; printf(&quot;pwnd %d\n&quot;, 42)</span> <span class="nf">movl</span> <span class="kc">$</span><span class="mi">0</span><span class="p">,</span> <span class="o">%</span><span class="nb">eax</span> <span class="c1">; ret = 0</span> <span class="nf">jmp</span> <span class="nv">.L5</span> <span class="c1">; goto .L5</span> <span class="nl">.L2:</span> <span class="nf">.cfi_def_cfa_offset</span> <span class="mi">8</span> <span class="nf">movl</span> <span class="kc">$</span><span class="mi">42</span><span class="p">,</span> <span class="mi">0</span> <span class="c1">; segfault (dereference addr 0)</span> <span class="nf">movl</span> <span class="kc">$</span><span class="mi">1</span><span class="p">,</span> <span class="o">%</span><span class="nb">eax</span> <span class="c1">; ret = 1</span> <span class="nf">ret</span> <span class="nl">.L5:</span> <span class="nf">.cfi_def_cfa_offset</span> <span class="mi">16</span> <span class="nf">addq</span> <span class="kc">$</span><span class="mi">8</span><span class="p">,</span> <span class="o">%</span><span class="nb">rsp</span> <span class="nf">.cfi_def_cfa_offset</span> <span class="mi">8</span> <span class="nf">ret</span> <span class="c1">; return ret</span> <span class="nf">.cfi_endproc</span> <span class="nl">.LFE11:</span> <span class="nf">.size</span> <span class="nv">main</span><span class="p">,</span> <span class="nv">.</span><span class="o">-</span><span class="nv">main</span> <span class="nf">.ident</span> <span class="s">&quot;GCC: (Debian 4.7.2-5) 4.7.2&quot;</span> <span class="nf">.section</span> <span class="nv">.note.GNU</span><span class="o">-</span><span class="nv">stack</span><span class="p">,</span><span class="s">&quot;&quot;</span><span class="p">,</span><span class="err">@</span><span class="nv">progbits</span> </pre></div> </td></tr></table> <p>Là, le compilateur a réorganisé le code. Si je devais le retraduire en C, j’écrirais ceci :</p> <div class="highlight"><pre><code class="language-c" data-lang="c"><span class="cp">#include &lt;stdio.h&gt;</span> <span class="cp">#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">&quot;pwnd %d</span><span class="se">\n</span><span class="s">&quot;</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></div> <p>Ce qui est amusant, c’est qu’il alloue de la mémoire pour stocker <code>i</code>, il lui affecte la valeur <code>42</code>… mais ne la lit jamais. En effet, il a décidé de recoder en dur <code>42</code> pour le paramètre du <code>printf</code>.</p> <p>Mais avec ce résultat, impossible d’atteindre le <code>printf</code> si <code>argc == 1</code>.</p> <h3 id="avec-gcc--o2">Avec <code>gcc -O2</code></h3> <p>Optimisons davantage :</p> <div class="highlight"><pre><code class="language-bash" data-lang="bash">gcc -O2 -S undefined.c</code></pre></div> <p>Ou, plus précisément (avec <code>gcc 4.9.1</code> par exemple, l’option <code>-O2</code> ne suffit pas) :</p> <div class="highlight"><pre><code class="language-bash" data-lang="bash">gcc -O -ftree-vrp -fdelete-null-pointer-checks -S undefined.c</code></pre></div> <p>(les options d’optimisation sont décrites dans la <a href="https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html">doc</a>).</p> <table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><code class="language-nasm" data-lang="nasm"> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26</code></pre></div></td><td class="code"><div class="highlight"><pre><span class="nf">.file</span> <span class="s">&quot;undefined.c&quot;</span> <span class="nf">.section</span> <span class="nv">.rodata.str1.1</span><span class="p">,</span><span class="s">&quot;aMS&quot;</span><span class="p">,</span><span class="err">@</span><span class="nv">progbits</span><span class="p">,</span><span class="mi">1</span> <span class="nl">.LC0:</span> <span class="nf">.string</span> <span class="s">&quot;pwnd %d\n&quot;</span> <span class="nf">.section</span> <span class="nv">.text.startup</span><span class="p">,</span><span class="s">&quot;ax&quot;</span><span class="p">,</span><span class="err">@</span><span class="nv">progbits</span> <span class="nf">.p2align</span> <span class="mi">4</span><span class="p">,,</span><span class="mi">15</span> <span class="nf">.globl</span> <span class="nv">main</span> <span class="nf">.type</span> <span class="nv">main</span><span class="p">,</span> <span class="err">@</span><span class="nv">function</span> <span class="nl">main:</span> <span class="nl">.LFB11:</span> <span class="nf">.cfi_startproc</span> <span class="nf">subq</span> <span class="kc">$</span><span class="mi">8</span><span class="p">,</span> <span class="o">%</span><span class="nb">rsp</span> <span class="nf">.cfi_def_cfa_offset</span> <span class="mi">16</span> <span class="nf">movl</span> <span class="kc">$</span><span class="mi">42</span><span class="p">,</span> <span class="o">%</span><span class="nb">esi</span> <span class="c1">; arg1 = 42</span> <span class="nf">movl</span> <span class="kc">$</span><span class="nv">.LC0</span><span class="p">,</span> <span class="o">%</span><span class="nb">edi</span> <span class="c1">; arg2 points to &quot;pwnd %d\n&quot;</span> <span class="nf">xorl</span> <span class="o">%</span><span class="nb">eax</span><span class="p">,</span> <span class="o">%</span><span class="nb">eax</span> <span class="nf">call</span> <span class="nv">printf</span> <span class="c1">; printf(&quot;pwnd %d\n&quot;, 42)</span> <span class="nf">xorl</span> <span class="o">%</span><span class="nb">eax</span><span class="p">,</span> <span class="o">%</span><span class="nb">eax</span> <span class="c1">; ret = 0</span> <span class="nf">addq</span> <span class="kc">$</span><span class="mi">8</span><span class="p">,</span> <span class="o">%</span><span class="nb">rsp</span> <span class="nf">.cfi_def_cfa_offset</span> <span class="mi">8</span> <span class="nf">ret</span> <span class="c1">; return ret</span> <span class="nf">.cfi_endproc</span> <span class="nl">.LFE11:</span> <span class="nf">.size</span> <span class="nv">main</span><span class="p">,</span> <span class="nv">.</span><span class="o">-</span><span class="nv">main</span> <span class="nf">.ident</span> <span class="s">&quot;GCC: (Debian 4.7.2-5) 4.7.2&quot;</span> <span class="nf">.section</span> <span class="nv">.note.GNU</span><span class="o">-</span><span class="nv">stack</span><span class="p">,</span><span class="s">&quot;&quot;</span><span class="p">,</span><span class="err">@</span><span class="nv">progbits</span> </pre></div> </td></tr></table> <p>Là, l’optimisation donne un résultat beaucoup plus direct :</p> <div 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">&quot;pwnd %d</span><span class="se">\n</span><span class="s">&quot;</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></div> <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>undefined.c</code>, soit <code>i</code> est <code>NULL</code>, soit <code>i</code> n’est pas <code>NULL</code>. Le compilateur sait que déréférencer un pointeur <code>NULL</code> est <em>indéfini</em>. Il n’a donc pas à gérer ce cas. Il considère donc que <code>i</code> est forcément non-<code>NULL</code>.</p> <p>Mais alors, à quoi bon tester si <code>i</code> est non-<code>NULL</code> ligne 7 ? Le test ne sert à rien. Donc il le supprime.</p> <p>Ce raisonnement permet de transformer le code ainsi :</p> <div class="highlight"><pre><code class="language-c" data-lang="c"><span class="cp">#include &lt;stdio.h&gt;</span> <span class="cp">#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">&quot;pwnd %d</span><span class="se">\n</span><span class="s">&quot;</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></div> <p>Mais ce n’est pas tout. Le compilateur sait que <code>i</code> n’est pas <code>NULL</code>, donc il peut considérer que le <code>malloc</code> a lieu. Et allouer un entier en mémoire, écrire <code>42</code> dedans, puis lire la valeur cet entier plus tard, ça se simplifie beaucoup : juste lire <code>42</code>, sans allouer de mémoire.</p> <p>Ce qu’il simplifie en :</p> <div class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nb">printf</span><span class="o">(</span><span class="s2">&quot;pwnd %d\n&quot;</span>, 42<span class="o">)</span><span class="p">;</span></code></pre></div> <p>CQFD.</p> <h3 id="avec-clang--02">Avec <code>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> <div class="highlight"><pre><code class="language-bash" data-lang="bash">clang -O2 -S undefined.c</code></pre></div> <p>Voici le résultat :</p> <table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><code class="language-nasm" data-lang="nasm"> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52</code></pre></div></td><td class="code"><div class="highlight"><pre><span class="nf">.file</span> <span class="s">&quot;undefined.c&quot;</span> <span class="nf">.text</span> <span class="nf">.globl</span> <span class="nv">main</span> <span class="nf">.align</span> <span class="mi">16</span><span class="p">,</span> <span class="mh">0x90</span> <span class="nf">.type</span> <span class="nv">main</span><span class="p">,</span><span class="err">@</span><span class="nv">function</span> <span class="nl">main:</span> <span class="err">#</span> <span class="err">@</span><span class="nf">main</span> <span class="nl">.Ltmp2:</span> <span class="nf">.cfi_startproc</span> <span class="err">#</span> <span class="nl">BB#0:</span> <span class="nf">pushq</span> <span class="o">%</span><span class="nb">rbp</span> <span class="nl">.Ltmp3:</span> <span class="nf">.cfi_def_cfa_offset</span> <span class="mi">16</span> <span class="nl">.Ltmp4:</span> <span class="nf">.cfi_offset</span> <span class="o">%</span><span class="nb">rbp</span><span class="p">,</span> <span class="o">-</span><span class="mi">16</span> <span class="nf">movq</span> <span class="o">%</span><span class="nb">rsp</span><span class="p">,</span> <span class="o">%</span><span class="nb">rbp</span> <span class="nl">.Ltmp5:</span> <span class="nf">.cfi_def_cfa_register</span> <span class="o">%</span><span class="nb">rbp</span> <span class="nf">cmpl</span> <span class="kc">$</span><span class="mi">1</span><span class="p">,</span> <span class="o">%</span><span class="nb">edi</span> <span class="c1">; if (argc == 1)</span> <span class="nf">je</span> <span class="nv">.LBB0_4</span> <span class="c1">; goto .LBB0_4</span> <span class="err">#</span> <span class="nl">BB#1:</span> <span class="nf">movl</span> <span class="kc">$</span><span class="mi">4</span><span class="p">,</span> <span class="o">%</span><span class="nb">edi</span> <span class="c1">; arg0 = 4 //sizeof(int)</span> <span class="nf">callq</span> <span class="nv">malloc</span> <span class="c1">; tmp = malloc(4)</span> <span class="nf">movq</span> <span class="o">%</span><span class="nb">rax</span><span class="p">,</span> <span class="o">%</span><span class="nb">rcx</span> <span class="c1">; i = tmp</span> <span class="nf">movl</span> <span class="kc">$</span><span class="mi">42</span><span class="p">,</span> <span class="p">(</span><span class="o">%</span><span class="nb">rcx</span><span class="p">)</span> <span class="c1">; *i = 42</span> <span class="nf">movl</span> <span class="kc">$</span><span class="mi">1</span><span class="p">,</span> <span class="o">%</span><span class="nb">eax</span> <span class="c1">; ret = 1</span> <span class="nf">testq</span> <span class="o">%</span><span class="nb">rcx</span><span class="p">,</span> <span class="o">%</span><span class="nb">rcx</span> <span class="c1">; if (!i)</span> <span class="nf">je</span> <span class="nv">.LBB0_3</span> <span class="c1">; goto .LBB0_3</span> <span class="err">#</span> <span class="nl">BB#2:</span> <span class="nf">movl</span> <span class="kc">$</span><span class="nv">.L.str</span><span class="p">,</span> <span class="o">%</span><span class="nb">edi</span> <span class="c1">; arg0 points to &quot;pwnd %d\n&quot;</span> <span class="nf">movl</span> <span class="kc">$</span><span class="mi">42</span><span class="p">,</span> <span class="o">%</span><span class="nb">esi</span> <span class="c1">; arg1 = 42</span> <span class="nf">xorb</span> <span class="o">%</span><span class="nb">al</span><span class="p">,</span> <span class="o">%</span><span class="nb">al</span> <span class="nf">callq</span> <span class="nv">printf</span> <span class="c1">; printf(&quot;pwnd %d\n&quot;, *i)</span> <span class="nf">xorl</span> <span class="o">%</span><span class="nb">eax</span><span class="p">,</span> <span class="o">%</span><span class="nb">eax</span> <span class="c1">; ret = 0</span> <span class="nl">.LBB0_3:</span> <span class="nf">popq</span> <span class="o">%</span><span class="nb">rbp</span> <span class="nf">ret</span> <span class="c1">; return ret</span> <span class="nl">.LBB0_4:</span> <span class="err">#</span> <span class="err">%</span><span class="nf">.thread</span> <span class="nf">ud2</span> <span class="c1">; undefined instruction</span> <span class="nl">.Ltmp6:</span> <span class="nf">.size</span> <span class="nv">main</span><span class="p">,</span> <span class="nv">.Ltmp6</span><span class="o">-</span><span class="nv">main</span> <span class="nl">.Ltmp7:</span> <span class="nf">.cfi_endproc</span> <span class="nl">.Leh_func_end0:</span> <span class="nf">.type</span> <span class="nv">.L.str</span><span class="p">,</span><span class="err">@</span><span class="nv">object</span> <span class="err">#</span> <span class="err">@</span><span class="nv">.str</span> <span class="nf">.section</span> <span class="nv">.rodata.str1.1</span><span class="p">,</span><span class="s">&quot;aMS&quot;</span><span class="p">,</span><span class="err">@</span><span class="nv">progbits</span><span class="p">,</span><span class="mi">1</span> <span class="nl">.L.str:</span> <span class="nf">.asciz</span> <span class="s">&quot;pwnd %d\n&quot;</span> <span class="nf">.size</span> <span class="nv">.L.str</span><span class="p">,</span> <span class="mi">9</span> <span class="nf">.section</span> <span class="s">&quot;.note.GNU-stack&quot;</span><span class="p">,</span><span class="s">&quot;&quot;</span><span class="p">,</span><span class="err">@</span><span class="nv">progbits</span> </pre></div> </td></tr></table> <p>Il réalise les mêmes optimisations que <code>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>ud2</code></a>.</p> <div class="highlight"><pre><code class="language-c" data-lang="c"><span class="cp">#include &lt;stdio.h&gt;</span> <span class="cp">#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">&quot;pwnd %d</span><span class="se">\n</span><span class="s">&quot;</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></div> <p>Étonnamment, <em>Clang</em> ne prend jamais la décision de supprimer le <code>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> <pre><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> <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>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> <table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><code class="language-java" data-lang="java"> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20</code></pre></div></td><td class="code"><div class="highlight"><pre><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">&quot;: keepRunning false&quot;</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">&quot;: start&quot;</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">&quot;: end&quot;</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> </pre></div> </td></tr></table> <p>Pour le compiler et l’exécuter :</p> <div class="highlight"><pre><code class="language-bash" data-lang="bash">javac VisibilityTest.java <span class="o">&amp;&amp;</span> java VisibilityTest</code></pre></div> <p>Sans synchronisation, il est très fort probable que le <em>thread</em> démarré ne se termine jamais, voyant toujours <code>keepRunning</code> à <code>true</code>, même si le thread principal lui a donné la valeur <code>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>keepRunning</code> <a href="http://www.javamex.com/tutorials/synchronization_volatile.shtml"><code>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>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>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>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>AImageView</code> (qui hérite d’<code>ImageView</code>) avec un mécanisme alternatif au <em>scale type</em>.</p> <h2 id="principes">Principes</h2> <p><code>AImageView</code> propose 4 paramètres :</p> <ul> <li><code>xWeight</code> et <code>yWeight</code> (des <code>float</code>s entre <code>0</code> et <code>1</code>) indiquent à quel endroit lier l’image au conteneur ;</li> <li><code>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>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> <div 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">&quot;match_parent&quot;</span> <span class="na">android:layout_height=</span><span class="s">&quot;match_parent&quot;</span> <span class="na">android:src=</span><span class="s">&quot;@drawable/myimage&quot;</span> <span class="na">app:xWeight=</span><span class="s">&quot;0.5&quot;</span> <span class="na">app:yWeight=</span><span class="s">&quot;0.5&quot;</span> <span class="na">app:fit=</span><span class="s">&quot;inside&quot;</span> <span class="na">app:scale=</span><span class="s">&quot;downscale|upscale&quot;</span> <span class="nt">/&gt;</span></code></pre></div> <p>Ici, l’image va s’adapter à l’intérieur (<code>inside</code>) du composant (des marges seront ajoutées si nécessaires), exactement (l’image peut être réduite – <code>downscale</code> – ou agrandie – <code>upscale</code> – pour s’adapter) et sera centrée (<code>0.5</code>, <code>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>ScaleType</code></a> du composant standard <code>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>ScaleType.CENTER</code></h3> <div 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">&quot;0.5&quot;</span> <span class="na">app:yWeight=</span><span class="s">&quot;0.5&quot;</span> <span class="na">app:scale=</span><span class="s">&quot;disabled&quot;</span> <span class="nt">/&gt;</span> <span class="c">&lt;!-- app:fit ne fait rien quand scale vaut &quot;disabled&quot; --&gt;</span></code></pre></div> <h3 id="scaletypecentercrop"><code>ScaleType.CENTER_CROP</code></h3> <div 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">&quot;0.5&quot;</span> <span class="na">app:yWeight=</span><span class="s">&quot;0.5&quot;</span> <span class="na">app:fit=</span><span class="s">&quot;outside&quot;</span> <span class="na">app:scale=</span><span class="s">&quot;downscale|upscale&quot;</span> <span class="nt">/&gt;</span></code></pre></div> <h3 id="scaletypecenterinside"><code>ScaleType.CENTER_INSIDE</code></h3> <div 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">&quot;0.5&quot;</span> <span class="na">app:yWeight=</span><span class="s">&quot;0.5&quot;</span> <span class="na">app:fit=</span><span class="s">&quot;inside&quot;</span> <span class="na">app:scale=</span><span class="s">&quot;downscale&quot;</span> <span class="nt">/&gt;</span></code></pre></div> <h3 id="scaletypefitcenter"><code>ScaleType.FIT_CENTER</code></h3> <div 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">&quot;0.5&quot;</span> <span class="na">app:yWeight=</span><span class="s">&quot;0.5&quot;</span> <span class="na">app:fit=</span><span class="s">&quot;inside&quot;</span> <span class="na">app:scale=</span><span class="s">&quot;downscale|upscale&quot;</span> <span class="nt">/&gt;</span></code></pre></div> <h3 id="scaletypefitend"><code>ScaleType.FIT_END</code></h3> <div 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">&quot;1&quot;</span> <span class="na">app:yWeight=</span><span class="s">&quot;1&quot;</span> <span class="na">app:fit=</span><span class="s">&quot;inside&quot;</span> <span class="na">app:scale=</span><span class="s">&quot;downscale|upscale&quot;</span> <span class="nt">/&gt;</span></code></pre></div> <h3 id="scaletypefitstart"><code>ScaleType.FIT_START</code></h3> <div 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">&quot;0&quot;</span> <span class="na">app:yWeight=</span><span class="s">&quot;0&quot;</span> <span class="na">app:fit=</span><span class="s">&quot;inside&quot;</span> <span class="na">app:scale=</span><span class="s">&quot;downscale|upscale&quot;</span> <span class="nt">/&gt;</span></code></pre></div> <h3 id="scaletypefitxy"><code>ScaleType.FIT_XY</code></h3> <p>Cette configuration ne peut pas être reproduite en utilisant les paramètres d’<code>AImageView</code>, car ce composant préserve toujours l’aspect ratio.</p> <h3 id="scaletypematrix"><code>ScaleType.MATRIX</code></h3> <p><code>AImageView</code> hérite d’<code>ImageView</code> et force le <code>scaleType</code> à <code>ScaleType.MATRIX</code> (pour redimensionner et déplacer l’image). Par conséquent, il n’y a pas d’équivalent, <code>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> <div class="highlight"><pre><code class="language-bash" data-lang="bash">git clone http://git.rom1v.com/AImageView.git</code></pre></div> <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>.aar</code> grâce à la commande :</p> <div class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nb">cd </span>AImageView ./gradlew assembleRelease</code></pre></div> <p>Il sera généré dans <code>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> <div class="highlight"><pre><code class="language-bash" data-lang="bash">git clone --recursive http://git.rom1v.com/AImageViewSample.git</code></pre></div> <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>apk</code> de debug (par exemple) :</p> <div class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nb">cd </span>AImageViewSample ./gradlew assembleDebug</code></pre></div> <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>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>activity_main.xml</code></a>.</p> <p>N’hésitez pas à critiquer ou à remonter des bugs.</p> Chiffrer un disque dur externe (ou une clé USB) avec LUKS 2014-07-20T21:03:33+02:00 http://blog.rom1v.com/2014/07/chiffrer-un-disque-dur-externe-ou-une-cle-usb-avec-luks <p>Un disque dur externe contenant vos données n’a pas de raisons de ne pas être chiffré. Voici quelques commandes utiles pour l’utilisation de <a href="http://fr.wikipedia.org/wiki/LUKS">LUKS</a>.</p> <h2 id="prrequis">Prérequis</h2> <p>Le paquet <code>cryptsetup</code> doit être installé :</p> <div class="highlight"><pre><code class="language-bash" data-lang="bash">sudo apt-get install cryptsetup</code></pre></div> <h2 id="initialisation">Initialisation</h2> <h3 id="trouver-le-disque">Trouver le disque</h3> <p>Tout d’abord, il faut déterminer l’emplacement du disque dur dans <code>/dev</code>. Pour cela, avant de le brancher, exécuter la commande :</p> <div class="highlight"><pre><code class="language-bash" data-lang="bash">sudo tail -f /var/log/messages</code></pre></div> <p>Lors du branchement du disque, plusieurs lignes similaires à celles-ci doivent apparaître :</p> <pre><code>Jul 20 21:25:29 pc kernel: [ 678.139988] sd 7:0:0:0: [sdb] 976754645 4096-byte logical blocks: (4.00 TB/3.63 TiB) </code></pre> <p>Ici, <code>[sdb]</code> signifie que l’emplacement est <code>/dev/sdb</code>. Dans la suite, je noterai cet emplacement <code>/dev/XXX</code>.</p> <p><em>Il est très important de ne pas se tromper d’emplacement, afin de ne pas formater un autre disque…</em></p> <h3 id="effacer-le-disque">Effacer le disque</h3> <p>Si des données étaient présentes sur ce disque, il est plus sûr de tout supprimer physiquement :</p> <div class="highlight"><pre><code class="language-bash" data-lang="bash">sudo dd <span class="k">if</span><span class="o">=</span>/dev/zero <span class="nv">of</span><span class="o">=</span>/dev/XXX <span class="nv">bs</span><span class="o">=</span>4K</code></pre></div> <p><em>Cette commande peut prendre beaucoup de temps, puisqu’elle consiste à réécrire physiquement tous les octets du disque dur.</em></p> <h3 id="crer-la-partition-chiffre">Créer la partition chiffrée</h3> <p>Pour initialiser la partition chiffrée :</p> <div class="highlight"><pre><code class="language-bash" data-lang="bash">sudo cryptsetup luksFormat -h sha256 /dev/XXX</code></pre></div> <p>La passphrase de déchiffrement sera demandée.</p> <p>Maintenant que nous avons une partition chiffrée, ouvrons-la :</p> <div class="highlight"><pre><code class="language-bash" data-lang="bash">sudo cryptsetup luksOpen /dev/XXX lenomquevousvoulez</code></pre></div> <p>Cette commande crée un nouveau <em>device</em> dans <code>/dev/mapper/lenomquevousvoulez</code>, contenant la version déchiffrée (en direct).</p> <h3 id="formater">Formater</h3> <p>Pour formater cette partition en <a href="https://fr.wikipedia.org/wiki/Ext4">ext4</a> :</p> <div class="highlight"><pre><code class="language-bash" data-lang="bash">sudo mkfs.ext4 /dev/mapper/lenomquevousvoulez</code></pre></div> <p>Pour l’initialisation, c’est fini, nous pouvons fermer la vue déchiffrée :</p> <div class="highlight"><pre><code class="language-bash" data-lang="bash">cryptsetup luksClose lenomquevousvoulez</code></pre></div> <h2 id="montage-manuel">Montage manuel</h2> <p>Il est possible de déchiffrer et monter la partition manuellement en ligne de commande :</p> <div class="highlight"><pre><code class="language-bash" data-lang="bash">sudo cryptsetup luksOpen /dev/XXX lenomquevousvoulez sudo mkdir -p /media/mydisk sudo mount -t ext4 /dev/mapper/lenomquevousvoulez /media/mydisk</code></pre></div> <p>Le contenu est alors accessible dans <code>/media/mydisk</code>.</p> <p>Pour démonter et fermer, c’est le contraire :</p> <div class="highlight"><pre><code class="language-bash" data-lang="bash">sudo umount /media/mydisk sudo cryptsetup luksClose /dev/XXX lenomquevousvoulez</code></pre></div> <p>Mais c’est un peu fastidieux. Et je n’ai pas trouvé de solution pour permettre le <code>luksOpen</code> par un utilisateur (non-root) en ligne de commande.</p> <h2 id="montage-semi-automatique">Montage semi-automatique</h2> <p>Les environnement de bureau permettent parfois de monter un disque dur chiffré simplement, avec la demande de la passphrase lors de l’ouverture du disque. Voici ce que j’obtiens avec <a href="http://www.xfce.org/">XFCE</a> :</p> <p class="center"><img src="/assets/luks/luksOpen.png" alt="luksOpen" /></p> <p>Mais par défaut, le nom du point de montage est peu pratique : <code>/media/rom/ae74bc79-9efe-4325-8b4d-63d1506fa928</code>. Heureusement, il est possible de le changer. Pour cela, il faut déterminer le nom de la partition déchiffrée :</p> <pre><code>$ ls /dev/mapper/luks-* /dev/mapper/luks-8b927433-4d4f-4636-8a76-06d18c09723e </code></pre> <p>Le nom très long correspond en fait à l’UUID du disque, qui peut aussi être récupéré grâce à :</p> <div class="highlight"><pre><code class="language-bash" data-lang="bash">sudo cryptsetup luksUUID /dev/XXX</code></pre></div> <p>ou encore :</p> <div class="highlight"><pre><code class="language-bash" data-lang="bash">sudo blkid /dev/XXX</code></pre></div> <p>L’emplacement désiré, ainsi que les options qui-vont-bien, doivent être rajoutés dans <code>/etc/fstab</code> :</p> <pre><code>/dev/mapper/luks-8b927433-4d4f-4636-8a76-06d18c09723e /media/mydisk ext4 user,noauto </code></pre> <p>Ainsi, le disque sera désormais monté dans <code>/media/mydisk</code>.</p> <p>Si en plus, nous souhaitons spécifier un nom <em>user-friendly</em> pour la partition déchiffrée (celui dans <code>/dev/mapper/</code>), il faut ajouter une ligne dans <code>/etc/crypttab</code> (en adaptant l’UUID) :</p> <pre><code>mydisk UUID=8b927433-4d4f-4636-8a76-06d18c09723e none luks,noauto </code></pre> <p>Et utiliser celle-ci à la place dans <code>/etc/fstab</code> :</p> <pre><code>/dev/mapper/mydisk /media/mydisk ext4 user,noauto </code></pre> <h2 id="gestion-des-passphrases">Gestion des passphrases</h2> <p>Il est possible d’utiliser plusieurs passphrases (jusqu’à 8) pour déchiffrer le même disque.</p> <p>Pour en ajouter une :</p> <div class="highlight"><pre><code class="language-bash" data-lang="bash">sudo cryptsetup luksAddKey /dev/XXX</code></pre></div> <p>Pour en supprimer une :</p> <div class="highlight"><pre><code class="language-bash" data-lang="bash">sudo cryptsetup luksRemoveKey /dev/XXX</code></pre></div> <p>Pour changer une unique passphrase, il suffit d’en ajouter une nouvelle puis de supprimer l’ancienne.</p> <p>Ou alors d’utiliser :</p> <div class="highlight"><pre><code class="language-bash" data-lang="bash">sudo cryptsetup luksChangeKey /dev/XXX</code></pre></div> <p>mais <code>man cryptsetup</code> dit qu’il y a un risque.</p> <h2 id="tat">État</h2> <p>Pour consulter l’état d’une partition LUKS :</p> <div class="highlight"><pre><code class="language-bash" data-lang="bash">sudo cryptsetup luksDump /dev/XXX</code></pre></div> <h2 id="gestion-de-len-tte">Gestion de l’en-tête</h2> <p>L’en-tête LUKS est écrit au début du disque. L’écraser empêche définivement le déchiffrement de la partition.</p> <p>Il est possible d’en faire une sauvegarde dans un fichier :</p> <div class="highlight"><pre><code class="language-bash" data-lang="bash">cryptsetup luksHeaderBackup /dev/XXX --header-backup-file fichier</code></pre></div> <p>Et de les restaurer :</p> <div class="highlight"><pre><code class="language-bash" data-lang="bash">cryptsetup luksHeaderRestore /dev/XXX --header-backup-file fichier</code></pre></div> <p>Pour supprimer l’en-tête (et donc rendre les données définitivement inaccessibles s’il n’y a pas de backup) :</p> <div class="highlight"><pre><code class="language-bash" data-lang="bash">cryptsetup luksErase /dev/XXX</code></pre></div> <h2 id="conclusion">Conclusion</h2> <p>Une fois configuré la première fois, et après les quelques modifications pénibles pour choisir les noms pour le déchiffrement et le montage, l’utilisation au quotidien est vraiment très simple : il suffit de rentrer la passphrase directement à partir du navigateur de fichiers.</p> SSHFS inversé (rsshfs) 2014-06-15T13:30:27+02:00 http://blog.rom1v.com/2014/06/sshfs-inverse-rsshfs <p><a href="https://fr.wikipedia.org/wiki/SSHFS">SSHFS</a> permet de monter un répertoire d’une machine distance dans l’arborescence locale en utilisant <a href="https://fr.wikipedia.org/wiki/Secure_Shell">SSH</a> :</p> <div class="highlight"><pre><code class="language-bash" data-lang="bash">sshfs serveur:/répertoire/distant /répertoire/local</code></pre></div> <p>Mais <strong>comment monter un répertoire local sur une machine distante ?</strong></p> <p>Une solution simple serait de se connecter en <em>SSH</em> sur la machine distante et d’exécuter la commande <code>sshfs</code> classique.</p> <p>Mais d’abord, ce n’est pas toujours directement <strong>possible</strong> : la machine locale peut ne pas être accessible (non adressable) depuis la machine distante. <em>Ça se contourne en créant un tunnel SSH utilisant la redirection de port distante (option <code>-R</code>).</em></p> <p>Et surtout, ce n’est pas toujours <strong>souhaitable</strong> : cela nécessite que la clé privée autorisée sur la machine locale soit connue de la machine distante. Or, dans certains cas, nous ne voulons pas qu’une machine <em>esclave</em> puisse se connecter à notre machine <em>maître</em>.</p> <h2 id="reverse-sshfs">Reverse SSHFS</h2> <p>En me basant sur <a href="https://sourceforge.net/p/fuse/mailman/message/27034864/">la commande donnée en exemple</a>, j’ai donc écrit un petit script <em>Bash</em> (<code>rsshfs</code>, licence <a href="http://www.gnu.org/licenses/quick-guide-gplv3.fr.html">GPLv3</a>) qui permet le <em>reverse SSHFS</em> :</p> <p>(disponible également sur <a href="https://github.com/rom1v/rsshfs">github</a>)</p> <div class="highlight"><pre><code class="language-bash" data-lang="bash">git clone http://git.rom1v.com/rsshfs.git <span class="nb">cd </span>rsshfs sudo install rsshfs /usr/local/bin</code></pre></div> <p><em>Les paquets <code>sshfs</code> et <code>fuse</code> doivent être installés sur la machine distante (et l’utilisateur doit appartenir au groupe <code>fuse</code>). Le paquet <code>openssh-sftp-server</code> doit être installé sur la machine locale.</em></p> <p>Son utilisation se veut similaire à celle de <code>sshfs</code> :</p> <div class="highlight"><pre><code class="language-bash" data-lang="bash">rsshfs /répertoire/local serveur:/répertoire/distant</code></pre></div> <p>Comme avec <code>sshfs</code>, <code>/répertoire/distant</code> doit exister sur <code>serveur</code> et doit être vide.</p> <p>Il est également possible de monter le répertoire en <em>lecture seule</em> :</p> <div class="highlight"><pre><code class="language-bash" data-lang="bash">rsshfs /répertoire/local serveur:/répertoire/distant -o ro</code></pre></div> <p>Contrairement à <code>sshfs</code>, étant donné que <code>rsshfs</code> agit comme un serveur, cette commande ne retourne pas tant que le répertoire distant n’est pas démonté.</p> <p>Pour démonter, dans un autre terminal :</p> <div class="highlight"><pre><code class="language-bash" data-lang="bash">rsshfs -u serveur:/répertoire/distant</code></pre></div> <p>Ou plus simplement en pressant <code>Ctrl+C</code> dans le terminal de la commande de montage.</p> <h2 id="amlioration">Amélioration</h2> <p>J’ai choisi la facilité en écrivant un script indépendant qui appelle la commande qui-va-bien.</p> <p>L’idéal serait d’ajouter cette fonctionnalité à <a href="https://github.com/libfuse/sshfs">sshfs</a> directement.</p> Compiler un exécutable pour Android 2014-03-18T23:39:52+01:00 http://blog.rom1v.com/2014/03/compiler-un-executable-pour-android <p>Je vais présenter dans ce billet comment compiler un exécutable <a href="https://fr.wikipedia.org/wiki/Architecture_ARM">ARM</a> pour <em>Android</em>, l’intégrer à un <a href="https://en.wikipedia.org/wiki/APK_%28file_format%29">APK</a> et l’utiliser dans une application.</p> <p>À titre d’exemple, nous allons intégrer un programme natif, <em>udpxy</em>, dans une application minimale de lecture vidéo.</p> <h2 id="contexte">Contexte</h2> <p>Le framework multimédia d’<em>Android</em> <a href="https://developer.android.com/guide/appendix/media-formats.html">ne supporte pas</a> nativement la lecture de flux <a href="https://fr.wikipedia.org/wiki/Multicast">UDP multicast</a> (<a href="http://stackoverflow.com/questions/8313995/udp-video-streaming-on-android">1</a>, <a href="http://stackoverflow.com/questions/8656199/open-udp-multicast-video-stream-on-android">2</a>).</p> <p>Il est possible, pour y parvenir, d’utiliser des lecteurs alternatifs, par exemple basés sur <a href="https://fr.wikipedia.org/wiki/FFmpeg">ffmpeg</a>/<a href="https://en.wikipedia.org/wiki/Libav">libav</a> (l’un est un <a href="http://blog.pkh.me/p/13-the-ffmpeg-libav-situation.html">fork</a> de l’autre) ou <a href="https://bitbucket.org/edwardcw/libvlc-android-sample">libvlc</a>.</p> <p>Il existe par ailleurs un outil natif, sous licence <a href="http://www.gnu.org/licenses/quick-guide-gplv3.fr.html">GPLv3</a>, relayant du trafic UDP multicast vers du HTTP : <a href="http://www.udpxy.com/index-en.html">udpxy</a>. N’importe quel client supportant HTTP (comme le lecteur natif d’<em>Android</em>) peut alors s’y connecter. C’est cet outil que nous allons utiliser ici.</p> <h2 id="udpxy">udpxy</h2> <h3 id="compilation-classique">Compilation classique</h3> <p>Avant de l’intégrer, comprenons son utilisation en le faisant tourner sur un ordinateur classique (<em>Debian Wheezy 64 bits</em> pour moi).</p> <p>Il faut d’abord le <a href="http://www.udpxy.com/download-en.html">télécharger les sources</a>, les extraire et compiler :</p> <div class="highlight"><pre><code class="language-bash" data-lang="bash">wget http://www.udpxy.com/download/1_23/udpxy.1.0.23-9-prod.tar.gz tar xf udpxy.1.0.23-9-prod.tar.gz <span class="nb">cd </span>udpxy-1.0.23-9/ make</code></pre></div> <p>Si tout se passe bien, nous obtenons (entre autres) un binaire <code>udpxy</code>.</p> <h3 id="test-de-diffusion">Test de diffusion</h3> <p>Pour tester, nous avons besoin d’une source UDP multicast. Ça tombe bien, VLC peut la fournir. Pour obtenir le résultat attendu par <em>udpxy</em>, nous devons diffuser vers une <a href="https://fr.wikipedia.org/wiki/Multicast#Adresses_multicast_IPv4_r.C3.A9serv.C3.A9es">adresse multicast</a> (ici <code>239.0.0.1</code>). Par exemple, à partir d’un fichier <a href="https://fr.wikipedia.org/wiki/Matroska">MKV</a> :</p> <div class="highlight"><pre><code class="language-bash" data-lang="bash">cvlc video.mkv <span class="s1">&#39;:sout=#udp{dst=239.0.0.1:1234}&#39;</span></code></pre></div> <p>En parallèle, démarrons une instance d’<code>udpxy</code>, que nous venons de compiler :</p> <div class="highlight"><pre><code class="language-bash" data-lang="bash">./udpxy -p 8379</code></pre></div> <p>Cette commande va démarrer un proxy relayant de l’UDP multicast vers de l’HTTP, écoutant sur le port 8379.</p> <p>Dans un autre terminal, nous pouvons faire pointer VLC sur le flux ainsi <em>proxifié</em> :</p> <div class="highlight"><pre><code class="language-bash" data-lang="bash">vlc http://localhost:8379/udp/239.0.0.1:1234/</code></pre></div> <p>Normalement, le flux doit être lu correctement.</p> <p>Remarquez qu’<em>udpxy</em> pourrait très bien être démarré sur une autre machine (il suffirait alors de remplacer <code>localhost</code> par son IP). Mais pour la suite, nous souhaiterons justement exécuter <em>udpxy</em> localement sur <em>Android</em>.</p> <p>Bien sûr, avec VLC, nous n’aurions pas besoin d’<em>udpxy</em>. Le flux est lisible directement avec la commande :</p> <div class="highlight"><pre><code class="language-bash" data-lang="bash">vlc udp://@239.0.0.1:1234/</code></pre></div> <h2 id="android">Android</h2> <p><em>Notez que certains devices Android <a href="http://code.google.com/p/android/issues/detail?id=51195">ne supportent pas le multicast</a>, la réception de flux multicast ne fonctionnera donc pas.</em></p> <p>Maintenant que nous avons vu comment fonctionne <em>udpxy</em>, portons-le sur <em>Android</em>.</p> <p>Notre but est de le contrôler à partir d’une application et le faire utiliser par le lecteur vidéo natif.</p> <p>Pour cela, plusieurs étapes sont nécessaires :</p> <ol> <li>obtenir un binaire ARM exécutable pour Android ;</li> <li>le packager avec une application ;</li> <li>l’extraire ;</li> <li>l’exécuter.</li> </ol> <h3 id="excutable-arm">Exécutable ARM</h3> <h4 id="pr-compil">Pré-compilé</h4> <p>Pour obtenir un binaire ARM exécutable, le plus simple, c’est évidemment de le récupérer déjà compilé, s’il est disponible (<a href="http://www.udpxy.com/download-en.html">c’est le cas</a> pour <em>udpxy</em>). Dans ce cas, il n’y a rien à faire.</p> <p>Pour le tester, transférons-le sur le téléphone et exécutons-le :</p> <div class="highlight"><pre><code class="language-bash" data-lang="bash">adb push udpxy /data/local/tmp adb shell /data/local/tmp/udpxy -p 8379</code></pre></div> <p>Si tout se passe bien, cette commande ne produit en apparence rien : elle attend qu’un client se connecte. Pour valider le fonctionnement, si le téléphone est sur le même réseau que votre ordinateur, vous pouvez utiliser cette instance (ARM) d’<em>udpxy</em> comme proxy entre la source multicast et un lecteur VLC local :</p> <div class="highlight"><pre><code class="language-bash" data-lang="bash">vlc http://xx.xx.xx.xx:8379/udp/239.0.0.1:1234/</code></pre></div> <p>Replacer <code>xx.xx.xx.xx</code> par l’ip du device, qu’il est possible d’obtenir ainsi :</p> <div class="highlight"><pre><code class="language-bash" data-lang="bash">adb shell netcfg <span class="p">|</span> grep UP</code></pre></div> <h4 id="compilation-ponctuelle">Compilation ponctuelle</h4> <p>S’il n’est pas disponible, il va falloir le compiler soi-même à partir des sources, ce qui nécessite le <a href="https://developer.android.com/ndk/index.html">NDK Android</a>, fournissant des <a href="https://fr.wikipedia.org/wiki/Cha%C3%AEne_de_compilation">chaînes de compilation</a> pré-compilées.</p> <p>Il suffit alors d’<a href="https://developer.android.com/ndk/guides/standalone_toolchain.html">initialiser</a> la variable d’environnement <code>CC</code> pour pointer sur la bonne chaîne de compilation (adaptez les chemins et l’architecture selon votre configuration) :</p> <div class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nb">export </span><span class="nv">NDK</span><span class="o">=</span>~/android/ndk <span class="nb">export </span><span class="nv">SYSROOT</span><span class="o">=</span><span class="s2">&quot;</span><span class="nv">$NDK</span><span class="s2">/platforms/android-19/arch-arm&quot;</span> <span class="nb">export </span><span class="nv">CC</span><span class="o">=</span><span class="s2">&quot;</span><span class="nv">$NDK</span><span class="s2">/toolchains/arm-linux-androideabi-4.8/prebuilt/linux-x86_64/bin/arm-linux-androideabi-gcc --sysroot=</span><span class="nv">$SYSROOT</span><span class="s2">&quot;</span> make</code></pre></div> <p>Bravo, vous venez de générer un binaire <code>udpxy</code> pour l’architecture ARM.</p> <h4 id="compilation-intgre">Compilation intégrée</h4> <p>La compilation telle que réalisée ci-dessus est bien adaptée à la génération d’un exécutable une fois de temps en temps, mais s’intègre mal dans un système de <a href="https://fr.wikipedia.org/wiki/Moteur_de_production"><em>build</em> automatisé</a>. En particulier, un utilisateur avec une architecture différente devra adapter les commandes à exécuter.</p> <p>Heureusement, le NDK permet une compilation plus générique.</p> <p>Pour <a href="https://developer.android.com/ndk/guides/index.html">cela</a>, il faut créer un répertoire <code>jni</code> dans un projet <em>Android</em> (ou n’importe où d’ailleurs, mais en pratique c’est là qu’il est censé être), y mettre les sources et écrire des <em>Makefiles</em>.</p> <p>Créons donc un répertoire <code>jni</code> contenant les sources. Vu que nous les avons déjà extraites, copions-les à la racine de <code>jni/</code> :</p> <div class="highlight"><pre><code class="language-bash" data-lang="bash">cp -rp udpxy-1.0.23-9/ jni/ <span class="nb">cd </span>jni/</code></pre></div> <p>Créons un <em>Makefile</em> nommé <a href="https://developer.android.com/ndk/guides/android_mk.html"><code>Android.mk</code></a> :</p> <div class="highlight"><pre><code class="language-makefile" data-lang="makefile"><span class="nv">LOCAL_PATH</span> <span class="o">:=</span> <span class="k">$(</span>call my-dir<span class="k">)</span> <span class="cp">include $(CLEAR_VARS)</span> <span class="nv">LOCAL_MODULE</span> <span class="o">:=</span> udpxy <span class="nv">LOCAL_SRC_FILES</span> <span class="o">:=</span> udpxy.c sloop.c rparse.c util.c prbuf.c ifaddr.c ctx.c <span class="se">\</span> mkpg.c rtp.c uopt.c dpkt.c netop.c extrn.c main.c <span class="cp">include $(BUILD_EXECUTABLE)</span></code></pre></div> <p>Puis compilons :</p> <div class="highlight"><pre><code class="language-bash" data-lang="bash">ndk-build</code></pre></div> <p><em><code>ndk-build</code> se trouve à la racine du NDK.</em></p> <p>Le binaire sera généré dans <code>libs/armeabi/udpxy</code>.</p> <p>Afin d’organiser les projets plus proprement, il vaut mieux mettre les sources d’<em>udpxy</em> et son <code>Android.mk</code> dans un sous-répertoire spécifique au projet (dans <code>jni/udpxy/</code>). Dans ce cas, il faut rajouter un fichier <code>jni/Android.mk</code> contenant :</p> <div class="highlight"><pre><code class="language-makefile" data-lang="makefile"><span class="cp">include $(call all-subdir-makefiles)</span></code></pre></div> <h3 id="packager-avec-lapplication">Packager avec l’application</h3> <p><em>Je suppose ici que vous savez déjà créer une application Android.</em></p> <p>Nous devons maintenant intégrer le binaire dans l’<a href="https://en.wikipedia.org/wiki/APK_%28file_format%29">APK</a>. Pour cela, il y a principalement <a href="http://stackoverflow.com/questions/5583608/difference-between-res-and-assets-directories">deux solutions</a> :</p> <ul> <li>l’intégrer aux <a href="http://developer.android.com/guide/topics/resources/providing-resources.html">ressources</a> (dans <code>res/raw/</code>) ;</li> <li>l’intégrer aux <a href="http://developer.android.com/reference/android/content/res/AssetManager.html">assets</a> (dans <code>assets/</code>).</li> </ul> <p>Vu que les <a href="https://developer.android.com/studio/projects/index.html#ApplicationModules">projets <em>library</em></a> ne gèrent pas les <em>assets</em>, nous allons utiliser une ressource <em>raw</em>.</p> <p>Il faut donc copier le binaire dans <code>res/raw/</code>, à chaque fois qu’il est généré (à automatiser donc).</p> <h3 id="extraire-lexcutable">Extraire l’exécutable</h3> <p>L’exécutable est bien packagé avec l’application, et comme toutes les <em>ressources</em>, nous pouvons facilement obtenir un <a href="http://developer.android.com/reference/android/content/res/Resources.html#openRawResource%28int%29"><code>InputStream</code></a> (le fonctionnement est <a href="http://developer.android.com/reference/android/content/res/AssetManager.html#open%28java.lang.String%29">similaire</a> pour les <em>assets</em>).</p> <p>Mais pour l’exécuter en natif, le binaire doit être présent sur le système de fichiers. Il faut donc le copier et lui donner les droits d’exécution. Sans la gestion des exceptions, cela donne :</p> <div class="highlight"><pre><code class="language-java" data-lang="java"><span class="c1">// &quot;/data/data/&lt;package&gt;/files/udpxy&quot;</span> <span class="n">File</span> <span class="n">target</span> <span class="o">=</span> <span class="k">new</span> <span class="n">File</span><span class="o">(</span><span class="n">getFilesDir</span><span class="o">(),</span> <span class="s">&quot;udpxy&quot;</span><span class="o">)</span> <span class="n">InputStream</span> <span class="n">in</span> <span class="o">=</span> <span class="n">getResources</span><span class="o">().</span><span class="na">openRawResource</span><span class="o">(</span><span class="n">R</span><span class="o">.</span><span class="na">raw</span><span class="o">.</span><span class="na">udpxy</span><span class="o">);</span> <span class="n">OutputStream</span> <span class="n">out</span> <span class="o">=</span> <span class="k">new</span> <span class="n">FileOutputStream</span><span class="o">(</span><span class="n">target</span><span class="o">);</span> <span class="c1">// copy from R.raw.udpxy to /data/data/&lt;package&gt;/files/udpxy</span> <span class="n">FileUtils</span><span class="o">.</span><span class="na">copy</span><span class="o">(</span><span class="n">in</span><span class="o">,</span> <span class="n">out</span><span class="o">);</span> <span class="c1">// make the file executable</span> <span class="n">FileUtils</span><span class="o">.</span><span class="na">chmod</span><span class="o">(</span><span class="n">target</span><span class="o">,</span> <span class="mi">0755</span><span class="o">);</span></code></pre></div> <p>Et les parties intéressantes de <code>FileUtils</code> :</p> <div class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">copy</span><span class="o">(</span><span class="n">InputStream</span> <span class="n">in</span><span class="o">,</span> <span class="n">OutputStream</span> <span class="n">os</span><span class="o">)</span> <span class="kd">throws</span> <span class="n">IOException</span> <span class="o">{</span> <span class="kt">byte</span><span class="o">[]</span> <span class="n">buf</span> <span class="o">=</span> <span class="k">new</span> <span class="kt">byte</span><span class="o">[</span><span class="mi">4096</span><span class="o">];</span> <span class="kt">int</span> <span class="n">read</span><span class="o">;</span> <span class="k">while</span> <span class="o">((</span><span class="n">read</span> <span class="o">=</span> <span class="o">(</span><span class="n">in</span><span class="o">.</span><span class="na">read</span><span class="o">(</span><span class="n">buf</span><span class="o">)))</span> <span class="o">!=</span> <span class="o">-</span><span class="mi">1</span><span class="o">)</span> <span class="o">{</span> <span class="n">os</span><span class="o">.</span><span class="na">write</span><span class="o">(</span><span class="n">buf</span><span class="o">,</span> <span class="mi">0</span><span class="o">,</span> <span class="n">read</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> <span class="kd">public</span> <span class="kd">static</span> <span class="kt">boolean</span> <span class="nf">chmod</span><span class="o">(</span><span class="n">File</span> <span class="n">file</span><span class="o">,</span> <span class="kt">int</span> <span class="n">mode</span><span class="o">)</span> <span class="kd">throws</span> <span class="n">IOException</span> <span class="o">{</span> <span class="n">String</span> <span class="n">sMode</span> <span class="o">=</span> <span class="n">String</span><span class="o">.</span><span class="na">format</span><span class="o">(</span><span class="s">&quot;%03o&quot;</span><span class="o">,</span> <span class="n">mode</span><span class="o">);</span> <span class="c1">// to string octal value</span> <span class="n">String</span> <span class="n">path</span> <span class="o">=</span> <span class="n">file</span><span class="o">.</span><span class="na">getAbsolutePath</span><span class="o">();</span> <span class="n">String</span><span class="o">[]</span> <span class="n">argv</span> <span class="o">=</span> <span class="o">{</span> <span class="s">&quot;chmod&quot;</span><span class="o">,</span> <span class="n">sMode</span><span class="o">,</span> <span class="n">path</span> <span class="o">};</span> <span class="k">try</span> <span class="o">{</span> <span class="k">return</span> <span class="n">Runtime</span><span class="o">.</span><span class="na">getRuntime</span><span class="o">().</span><span class="na">exec</span><span class="o">(</span><span class="n">argv</span><span class="o">).</span><span class="na">waitFor</span><span class="o">()</span> <span class="o">==</span> <span class="mi">0</span><span class="o">;</span> <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">InterruptedException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span> <span class="k">throw</span> <span class="k">new</span> <span class="n">IOException</span><span class="o">(</span><span class="n">e</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span></code></pre></div> <h3 id="excuter-le-programme-natif">Exécuter le programme natif</h3> <p>Maintenant que le binaire est disponible sur le système de fichiers, il suffit de l’exécuter :</p> <div class="highlight"><pre><code class="language-java" data-lang="java"><span class="n">String</span><span class="o">[]</span> <span class="n">command</span> <span class="o">=</span> <span class="o">{</span> <span class="n">udpxyBin</span><span class="o">.</span><span class="na">getAbsolutePath</span><span class="o">(),</span> <span class="s">&quot;-p&quot;</span><span class="o">,</span> <span class="s">&quot;8379&quot;</span> <span class="o">};</span> <span class="n">udpxyProcess</span> <span class="o">=</span> <span class="n">Runtime</span><span class="o">.</span><span class="na">getRuntime</span><span class="o">().</span><span class="na">exec</span><span class="o">(</span><span class="n">command</span><span class="o">);</span></code></pre></div> <p>Le lecteur vidéo pourra alors utiliser l’URI <em>proxifié</em> comme source de données :</p> <div class="highlight"><pre><code class="language-java" data-lang="java"><span class="n">String</span> <span class="n">src</span> <span class="o">=</span> <span class="n">UdpxyService</span><span class="o">.</span><span class="na">proxify</span><span class="o">(</span><span class="s">&quot;239.0.0.1:1234&quot;</span><span class="o">);</span></code></pre></div> <h2 id="projets">Projets</h2> <h3 id="andudpxy">andudpxy</h3> <p>Je mets à disposition sous licence <a href="http://www.gnu.org/licenses/quick-guide-gplv3.fr.html">GPLv3</a> le projet <em>library</em> <code>andudpxy</code>, qui met en œuvre ce que j’ai expliqué ici :</p> <pre><code>git clone http://git.rom1v.com/andudpxy.git </code></pre> <p>(ou sur <a href="https://github.com/rom1v/andudpxy">github</a>)</p> <p>Pour l’utiliser dans votre application, n’oubliez pas de référencer la <em>library</em> et de déclarer le service <code>UdpxyService</code> dans votre <code>AndroidManifest.xml</code> :</p> <div class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt">&lt;service</span> <span class="na">android:name=</span><span class="s">&quot;com.rom1v.andudpxy.UdpxyService&quot;</span> <span class="nt">/&gt;</span></code></pre></div> <p>Pour démarrer le <a href="https://fr.wikipedia.org/wiki/Daemon_%28informatique%29">démon</a> :</p> <div class="highlight"><pre><code class="language-java" data-lang="java"><span class="n">UdpxyService</span><span class="o">.</span><span class="na">startUdpxy</span><span class="o">(</span><span class="n">context</span><span class="o">);</span></code></pre></div> <p>et pour l’arrêter :</p> <div class="highlight"><pre><code class="language-java" data-lang="java"><span class="n">UdpxyService</span><span class="o">.</span><span class="na">stopUdpxy</span><span class="o">(</span><span class="n">context</span><span class="o">);</span></code></pre></div> <h3 id="andudpxy-sample">andudpxy-sample</h3> <p>J’ai également écrit une application minimale de lecture vidéo qui utilise cette <em>library</em> :</p> <pre><code>git clone http://git.rom1v.com/andudpxy-sample.git </code></pre> <p>(ou sur <a href="https://github.com/rom1v/andudpxy-sample">github</a>)</p> <p>C’est toujours utile d’avoir une application d’exemple censée fonctionner ;-)</p> <p>L’adresse du flux UDP multicast à lire est écrite en dur dans <code>MainActivity</code> (et le flux doit fonctionner lors du démarrage de l’activité) :</p> <div class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="n">String</span> <span class="n">ADDR</span> <span class="o">=</span> <span class="s">&quot;239.0.0.1:1234&quot;</span><span class="o">;</span></code></pre></div> <h3 id="compilation">Compilation</h3> <p>Après avoir cloné les 2 projets dans un même répertoire parent, renommez les <code>local.properties.sample</code> en <code>local.properties</code>, éditez-les pour indiquer le chemin du SDK et du NDK.</p> <p>Ensuite, allez dans le répertoire <code>andudpxy-sample</code>, puis exécutez :</p> <div class="highlight"><pre><code class="language-bash" data-lang="bash">ant clean debug</code></pre></div> <p>Vous devriez obtenir <code>bin/andudpxy-sample-debug.apk</code>.</p> <p>Bien sûr, vous pouvez aussi les importer dans <a href="https://fr.wikipedia.org/wiki/Eclipse_%28projet%29">Eclipse</a> (ou un autre <a href="https://fr.wikipedia.org/wiki/Environnement_de_d%C3%A9veloppement">IDE</a>) et les compiler selon vos habitudes.</p> <h2 id="conclusion">Conclusion</h2> <p>Nous avons réussi à compiler et exécuter un binaire ARM sur <em>Android</em>, packagé dans une application.</p> <p>Ceci peut être utile pour exécuter du code déjà implémenté nativement pour d’autres plates-formes, pour faire tourner un démon natif… Par exemple, le projet <a href="http://www.servalproject.org/">Serval</a> (sur lequel j’ai un peu <a href="/contrib/#servalbatphone">travaillé</a>) utilise un démon <code>servald</code>, qui tourne également sur d’autres architectures.</p> <p>Ce n’est cependant pas la seule manière d’exécuter du code natif dans une application : la plus courante est d’appeler des fonctions natives (et non un exécutable) directement à partir de <em>Java</em>, en utilisant <a href="https://fr.wikipedia.org/wiki/Java_Native_Interface">JNI</a>. L’une et l’autre répondent à des besoins différents.</p> Des slides Beamer en Markdown 2014-02-15T18:29:14+01:00 http://blog.rom1v.com/2014/02/des-slides-beamer-en-markdown <p>Pour produire des slides propres pour une présentation, j’aime beaucoup <a href="https://fr.wikipedia.org/wiki/Beamer">Beamer</a> (basé sur <a href="https://fr.wikipedia.org/wiki/LaTeX">LaTeX</a>). Mais la syntaxe est un peu lourde et la configuration est parfois inutilement compliquée (fonts, encodage, compilation multipasse…).</p> <p>Est-il possible d’avoir les avantages de <em>Beamer</em> sans ses inconvénients ? La réponse est <em>oui</em>, grâce à <a href="http://pandoc.org">pandoc</a> et son <a href="http://pandoc.org/MANUAL.html#pandocs-markdown">Markdown étendu</a>.</p> <h2 id="beamer">Beamer</h2> <p>Voici le code d’un exemple très simple de présentation <em>Beamer</em> :</p> <div class="highlight"><pre><code class="language-latex" data-lang="latex"><span class="k">\documentclass</span><span class="na">[hyperref={pdfpagelabels=false}]</span><span class="nb">{</span>beamer<span class="nb">}</span> <span class="k">\usepackage</span><span class="na">[utf8]</span><span class="nb">{</span>inputenc<span class="nb">}</span> <span class="k">\usepackage</span><span class="nb">{</span>lmodern<span class="nb">}</span> <span class="k">\title</span><span class="nb">{</span>Exemple<span class="nb">}</span> <span class="k">\author</span><span class="nb">{</span>Romain Vimont<span class="nb">}</span> <span class="k">\date</span><span class="nb">{</span>15 février 2014<span class="nb">}</span> <span class="k">\begin</span><span class="nb">{</span>document<span class="nb">}</span> <span class="k">\begin</span><span class="nb">{</span>frame<span class="nb">}</span> <span class="k">\titlepage</span> <span class="k">\end</span><span class="nb">{</span>frame<span class="nb">}</span> <span class="k">\section</span><span class="nb">{</span>Ma section<span class="nb">}</span> <span class="k">\begin</span><span class="nb">{</span>frame<span class="nb">}{</span>Ma première frame<span class="nb">}</span> <span class="k">\begin</span><span class="nb">{</span>itemize<span class="nb">}</span> <span class="k">\item</span> c&#39;est bien <span class="k">\item</span> mais c&#39;est verbeux <span class="k">\end</span><span class="nb">{</span>itemize<span class="nb">}</span> <span class="k">\end</span><span class="nb">{</span>frame<span class="nb">}</span> <span class="k">\end</span><span class="nb">{</span>document<span class="nb">}</span></code></pre></div> <p>Le code source est, il faut bien l’avouer, assez rebutant, et le rapport signal/bruit assez faible.</p> <p>Une fois les paquets <code>pdflatex</code>, <code>textlive-latex-base</code> et <code>latex-beamer</code> installés (sous <em>Debian</em>), vous pouvez le compiler avec :</p> <pre><code>pdflatex fichier.tex </code></pre> <h2 id="markdown">Markdown</h2> <p>Voici maintenant l’équivalent en <em>Pandoc-Markdown</em> :</p> <pre><code>% Exemple % Romain Vimont % 15 février 2014 # Ma section ## Ma première frame - c'est bien - et en plus ce n'est pas verbeux </code></pre> <p>Indiscutablement, c’est beaucoup plus lisible !</p> <p>Avec le paquet <code>pandoc</code> (en plus des paquets latex déjà installés), vous pouvez le compiler avec :</p> <pre><code>pandoc -st beamer fichier.md -o fichier.pdf </code></pre> <p><em>Notez que le résultat n’est pas strictement identique, la version compilée avec <code>pandoc</code> ajoute une frame de section, mais il ne s’agit que d’une différence de template par défaut.</em></p> <h2 id="dmo">Démo</h2> <p>J’ai créé une présentation d’exemple avec un thème personnalisé.</p> <p class="center"><img src="/assets/beamer/beamer.png" alt="beamer" /></p> <p>Le résultat est disponible <a href="http://dl.rom1v.com/mdbeamer/slides.pdf">ici</a>, mais c’est surtout la <a href="http://dl.rom1v.com/mdbeamer/slides.md.html">source</a> (<a href="http://dl.rom1v.com/mdbeamer/slides.md">raw</a>) qui est intéressante. Pour récupérer le projet et générer le pdf :</p> <div class="highlight"><pre><code class="language-bash" data-lang="bash">git clone http://git.rom1v.com/mdbeamer.git <span class="nb">cd </span>mdbeamer make</code></pre></div> <p>Il est également disponible sur <a href="https://github.com/rom1v/mdbeamer">github</a>.</p> <p>Ce projet a vocation à être utilisé comme base pour mes futures présentations (et les vôtres si vous le désirez). Chacune d’entre elles sera sur une <a href="http://gitref.org/branching/">branche</a> <em>git</em> et sur un <a href="http://gitref.org/remotes/">remote</a> différents.</p> <h2 id="injection-de-version">Injection de version</h2> <p>Pour pouvoir distinguer rapidement différentes versions d’une même présentation, j’ai également ajouté au <em>Makefile</em> une commande pour injecter un identifiant de version à côté de la date (donc à la fin de la 3e ligne du code source). Il s’agit du résultat de <code>git describe</code> (contenant le nom du dernier <a href="http://git-scm.com/book/ch2-6.html#Annotated-Tags">tag annoté</a>) ou à défaut simplement le numéro de commit actuel.</p> <p>Pour l’utiliser :</p> <pre><code>make withversion </code></pre> <h2 id="un-format-pivot">Un format pivot</h2> <p>J’utilise ici le <em>Pandoc-Markdown</em> pour écrire du <em>Beamer</em> plus simplement.</p> <p>Mais son intérêt est beaucoup plus général : il s’agit d’un véritable <strong>format pivot</strong>, compilable vers de nombreux formats.</p> <p>Pour de la documentation par exemple, il suffit de l’écrire en <em>Pandoc-Markdown</em> et de la compiler, grâce à <code>pandoc</code>, en :</p> <ul> <li><a href="https://fr.wikipedia.org/wiki/HTML5">html</a></li> <li><a href="https://fr.wikipedia.org/wiki/LaTeX">tex</a></li> <li><a href="https://fr.wikipedia.org/wiki/Portable_Document_Format">pdf</a></li> <li><a href="https://fr.wikipedia.org/wiki/MediaWiki">mediawiki</a></li> <li><a href="https://fr.wikipedia.org/wiki/DocBook">docbook</a></li> <li><a href="https://fr.wikipedia.org/wiki/EPUB_%28format%29">epub</a></li> <li><a href="https://fr.wikipedia.org/wiki/OpenDocument">odt</a></li> <li><a href="https://fr.wikipedia.org/wiki/Docx">docx</a></li> <li>…</li> </ul> <p>C’est d’ailleurs très pratique quand quelqu’un vous demande une documentation dans un format totalement inadapté (type <code>docx</code>), à rédiger de manière collaborative : il suffit alors d’utiliser <em>Pandoc-Markdown</em>, <em>git</em> et un <em>Makefile</em>.</p> <p>Pour les slides, <em>pandoc</em> supporte, en plus de <em>Beamer</em>, la compilation vers des slides HTML :</p> <ul> <li><a href="http://www.w3.org/Talks/Tools/">slidy</a></li> <li><a href="http://goessner.net/articles/slideous/">slideous</a></li> <li><a href="http://paulrouget.com/dzslides/">dzslides</a></li> <li><a href="http://lab.hakim.se/reveal-js/#/">revealjs</a></li> <li><a href="http://meyerweb.com/eric/tools/s5/">s5</a></li> </ul> <p>Cette généricité a bien sûr des limites : l’utilisation de code spécifique à un format particulier (tel que j’en utilise dans mon exemple) empêche de le compiler correctement vers d’autres formats.</p> <h2 id="conclusion">Conclusion</h2> <p>Le language <em>Markdown</em> (étendu par <em>pandoc</em>) permet de combiner la généricité, la simplicité et la _git_abilité pour écrire des documents ou des slides, ce qui en fait un outil absolument indispensable.</p>