﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-loop_in_codes-随笔分类-c/c++</title><link>http://www.cppblog.com/kevinlynx/category/6337.html</link><description>低调做技术__欢迎移步我的独立博客 &lt;a href="http://codemacro.com"&gt;codemaro.com&lt;/a&gt; 微博 &lt;a href="http://weibo.com/kevinlynx"&gt;kevinlynx&lt;/a&gt;</description><language>zh-cn</language><lastBuildDate>Tue, 05 May 2015 11:54:49 GMT</lastBuildDate><pubDate>Tue, 05 May 2015 11:54:49 GMT</pubDate><ttl>60</ttl><item><title>无锁有序链表的实现</title><link>http://www.cppblog.com/kevinlynx/archive/2015/05/05/210555.html</link><dc:creator>Kevin Lynx</dc:creator><author>Kevin Lynx</author><pubDate>Tue, 05 May 2015 11:47:00 GMT</pubDate><guid>http://www.cppblog.com/kevinlynx/archive/2015/05/05/210555.html</guid><wfw:comment>http://www.cppblog.com/kevinlynx/comments/210555.html</wfw:comment><comments>http://www.cppblog.com/kevinlynx/archive/2015/05/05/210555.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/kevinlynx/comments/commentRss/210555.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/kevinlynx/services/trackbacks/210555.html</trackback:ping><description><![CDATA[<div class="entry-content">
<p>无锁有序链表可以保证元素的唯一性，使其可用于哈希表的桶，甚至直接作为一个效率不那么高的map。普通链表的无锁实现相对简单点，因为插入元素可以在表头插，而有序链表的插入则是任意位置。</p>

<p>本文主要基于论文<a href="http://www.research.ibm.com/people/m/michael/spaa-2002.pdf">High Performance Dynamic Lock-Free Hash Tables</a>实现。</p>

<h2>主要问题</h2>

<p>链表的主要操作包含<code>insert</code>和<code>remove</code>，先简单实现一个版本，就会看到问题所在，以下代码只用作示例：</p>

<div class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="k">struct</span> <span class="kt">node_t</span> <span class="p">{</span>
        <span class="kt">key_t</span> <span class="n">key</span><span class="p">;</span>
        <span class="kt">value_t</span> <span class="n">val</span><span class="p">;</span>
        <span class="kt">node_t</span> <span class="o">*</span><span class="n">next</span><span class="p">;</span>
    <span class="p">};</span>

    <span class="kt">int</span> <span class="nf">l_find</span><span class="p">(</span><span class="kt">node_t</span> <span class="o">**</span><span class="n">pred_ptr</span><span class="p">,</span> <span class="kt">node_t</span> <span class="o">**</span><span class="n">item_ptr</span><span class="p">,</span> <span class="kt">node_t</span> <span class="o">*</span><span class="n">head</span><span class="p">,</span> <span class="kt">key_t</span> <span class="n">key</span><span class="p">)</span> <span class="p">{</span>
        <span class="kt">node_t</span> <span class="o">*</span><span class="n">pred</span> <span class="o">=</span> <span class="n">head</span><span class="p">;</span>
        <span class="kt">node_t</span> <span class="o">*</span><span class="n">item</span> <span class="o">=</span> <span class="n">head</span><span class="o">-&gt;</span><span class="n">next</span><span class="p">;</span>
        <span class="k">while</span> <span class="p">(</span><span class="n">item</span><span class="p">)</span> <span class="p">{</span>
            <span class="kt">int</span> <span class="n">d</span> <span class="o">=</span> <span class="n">KEY_CMP</span><span class="p">(</span><span class="n">item</span><span class="o">-&gt;</span><span class="n">key</span><span class="p">,</span> <span class="n">key</span><span class="p">);</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">d</span> <span class="o">&gt;=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
                <span class="o">*</span><span class="n">pred_ptr</span> <span class="o">=</span> <span class="n">pred</span><span class="p">;</span>
                <span class="o">*</span><span class="n">item_ptr</span> <span class="o">=</span> <span class="n">item</span><span class="p">;</span>
                <span class="k">return</span> <span class="n">d</span> <span class="o">==</span> <span class="mi">0</span> <span class="o">?</span> <span class="nl">TRUE</span> <span class="p">:</span> <span class="n">FALSE</span><span class="p">;</span>
            <span class="p">}</span>
            <span class="n">pred</span> <span class="o">=</span> <span class="n">item</span><span class="p">;</span>
            <span class="n">item</span> <span class="o">=</span> <span class="n">item</span><span class="o">-&gt;</span><span class="n">next</span><span class="p">;</span>
        <span class="p">}</span> 
        <span class="o">*</span><span class="n">pred_ptr</span> <span class="o">=</span> <span class="n">pred</span><span class="p">;</span>
        <span class="o">*</span><span class="n">item_ptr</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
        <span class="k">return</span> <span class="n">FALSE</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="kt">int</span> <span class="nf">l_insert</span><span class="p">(</span><span class="kt">node_t</span> <span class="o">*</span><span class="n">head</span><span class="p">,</span> <span class="kt">key_t</span> <span class="n">key</span><span class="p">,</span> <span class="kt">value_t</span> <span class="n">val</span><span class="p">)</span> <span class="p">{</span>
        <span class="kt">node_t</span> <span class="o">*</span><span class="n">pred</span><span class="p">,</span> <span class="o">*</span><span class="n">item</span><span class="p">,</span> <span class="o">*</span><span class="n">new_item</span><span class="p">;</span>
        <span class="k">while</span> <span class="p">(</span><span class="n">TRUE</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">l_find</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pred</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">item</span><span class="p">,</span> <span class="n">head</span><span class="p">,</span> <span class="n">key</span><span class="p">))</span> <span class="p">{</span>
                <span class="k">return</span> <span class="n">FALSE</span><span class="p">;</span>
            <span class="p">}</span>
            <span class="n">new_item</span> <span class="o">=</span> <span class="p">(</span><span class="kt">node_t</span><span class="o">*</span><span class="p">)</span> <span class="n">malloc</span><span class="p">(</span><span class="k">sizeof</span><span class="p">(</span><span class="kt">node_t</span><span class="p">));</span>
            <span class="n">new_item</span><span class="o">-&gt;</span><span class="n">key</span> <span class="o">=</span> <span class="n">key</span><span class="p">;</span>
            <span class="n">new_item</span><span class="o">-&gt;</span><span class="n">val</span> <span class="o">=</span> <span class="n">val</span><span class="p">;</span>
            <span class="n">new_item</span><span class="o">-&gt;</span><span class="n">next</span> <span class="o">=</span> <span class="n">item</span><span class="p">;</span>
            <span class="c1">// A. 如果pred本身被移除了</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">CAS</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pred</span><span class="o">-&gt;</span><span class="n">next</span><span class="p">,</span> <span class="n">item</span><span class="p">,</span> <span class="n">new_item</span><span class="p">))</span> <span class="p">{</span>
                <span class="k">return</span> <span class="n">TRUE</span><span class="p">;</span>
            <span class="p">}</span>
            <span class="n">free</span><span class="p">(</span><span class="n">new_item</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="kt">int</span> <span class="nf">l_remove</span><span class="p">(</span><span class="kt">node_t</span> <span class="o">*</span><span class="n">head</span><span class="p">,</span> <span class="kt">key_t</span> <span class="n">key</span><span class="p">)</span> <span class="p">{</span>
        <span class="kt">node_t</span> <span class="o">*</span><span class="n">pred</span><span class="p">,</span> <span class="o">*</span><span class="n">item</span><span class="p">;</span>
        <span class="k">while</span> <span class="p">(</span><span class="n">TRUE</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">l_find</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pred</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">item</span><span class="p">,</span> <span class="n">head</span><span class="p">,</span> <span class="n">key</span><span class="p">))</span> <span class="p">{</span>
                <span class="k">return</span> <span class="n">TRUE</span><span class="p">;</span>
            <span class="p">}</span>
            <span class="c1">// B. 如果pred被移除；如果item也被移除</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">CAS</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pred</span><span class="o">-&gt;</span><span class="n">next</span><span class="p">,</span> <span class="n">item</span><span class="p">,</span> <span class="n">item</span><span class="o">-&gt;</span><span class="n">next</span><span class="p">))</span> <span class="p">{</span>
                <span class="n">haz_free</span><span class="p">(</span><span class="n">item</span><span class="p">);</span>
                <span class="k">return</span> <span class="n">TRUE</span><span class="p">;</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span></code></pre></div>




<!-- more -->


<p><code>l_find</code>函数返回查找到的前序元素和元素本身，代码A和B虽然拿到了<code>pred</code>和<code>item</code>，但在<code>CAS</code>的时候，其可能被其他线程移除。甚至，在<code>l_find</code>过程中，其每一个元素都可能被移除。问题在于，<strong>任何时候拿到一个元素时，都不确定其是否还有效</strong>。元素的有效性包括其是否还在链表中，其指向的内存是否还有效。</p>

<h2>解决方案</h2>

<p><strong>通过为元素指针增加一个有效性标志位，配合CAS操作的互斥性</strong>，就可以解决元素有效性判定问题。</p>

<p>因为<code>node_t</code>放在内存中是会对齐的，所以指向<code>node_t</code>的指针值低几位是不会用到的，从而可以在低几位里设置标志，这样在做CAS的时候，就实现了DCAS的效果，相当于将两个逻辑上的操作变成了一个原子操作。想象下引用计数对象的线程安全性，其内包装的指针是线程安全的，但对象本身不是。</p>

<p>CAS的互斥性，在若干个线程CAS相同的对象时，只有一个线程会成功，失败的线程就可以以此判定目标对象发生了变更。改进后的代码（代码仅做示例用，不保证正确）：</p>

<div class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="k">typedef</span> <span class="kt">size_t</span> <span class="kt">markable_t</span><span class="p">;</span>
    <span class="c1">// 最低位置1，表示元素被删除</span>
    <span class="cp">#define HAS_MARK(p) ((markable_t)p &amp; 0x01)</span>
    <span class="cp">#define MARK(p) ((markable_t)p | 0x01)</span>
    <span class="cp">#define STRIP_MARK(p) ((markable_t)p &amp; ~0x01)</span>

    <span class="kt">int</span> <span class="nf">l_insert</span><span class="p">(</span><span class="kt">node_t</span> <span class="o">*</span><span class="n">head</span><span class="p">,</span> <span class="kt">key_t</span> <span class="n">key</span><span class="p">,</span> <span class="kt">value_t</span> <span class="n">val</span><span class="p">)</span> <span class="p">{</span>
        <span class="kt">node_t</span> <span class="o">*</span><span class="n">pred</span><span class="p">,</span> <span class="o">*</span><span class="n">item</span><span class="p">,</span> <span class="o">*</span><span class="n">new_item</span><span class="p">;</span>
        <span class="k">while</span> <span class="p">(</span><span class="n">TRUE</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">l_find</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pred</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">item</span><span class="p">,</span> <span class="n">head</span><span class="p">,</span> <span class="n">key</span><span class="p">))</span> <span class="p">{</span> 
                <span class="k">return</span> <span class="n">FALSE</span><span class="p">;</span>
            <span class="p">}</span>
            <span class="n">new_item</span> <span class="o">=</span> <span class="p">(</span><span class="kt">node_t</span><span class="o">*</span><span class="p">)</span> <span class="n">malloc</span><span class="p">(</span><span class="k">sizeof</span><span class="p">(</span><span class="kt">node_t</span><span class="p">));</span>
            <span class="n">new_item</span><span class="o">-&gt;</span><span class="n">key</span> <span class="o">=</span> <span class="n">key</span><span class="p">;</span>
            <span class="n">new_item</span><span class="o">-&gt;</span><span class="n">val</span> <span class="o">=</span> <span class="n">val</span><span class="p">;</span>
            <span class="n">new_item</span><span class="o">-&gt;</span><span class="n">next</span> <span class="o">=</span> <span class="n">item</span><span class="p">;</span>
            <span class="c1">// A. 虽然find拿到了合法的pred，但是在以下代码之前pred可能被删除，此时pred-&gt;next被标记</span>
            <span class="c1">//    pred-&gt;next != item，该CAS会失败，失败后重试</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">CAS</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pred</span><span class="o">-&gt;</span><span class="n">next</span><span class="p">,</span> <span class="n">item</span><span class="p">,</span> <span class="n">new_item</span><span class="p">))</span> <span class="p">{</span>
                <span class="k">return</span> <span class="n">TRUE</span><span class="p">;</span>
            <span class="p">}</span>
            <span class="n">free</span><span class="p">(</span><span class="n">new_item</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="k">return</span> <span class="n">FALSE</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="kt">int</span> <span class="nf">l_remove</span><span class="p">(</span><span class="kt">node_t</span> <span class="o">*</span><span class="n">head</span><span class="p">,</span> <span class="kt">key_t</span> <span class="n">key</span><span class="p">)</span> <span class="p">{</span>
        <span class="kt">node_t</span> <span class="o">*</span><span class="n">pred</span><span class="p">,</span> <span class="o">*</span><span class="n">item</span><span class="p">;</span>
        <span class="k">while</span> <span class="p">(</span><span class="n">TRUE</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">l_find</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pred</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">item</span><span class="p">,</span> <span class="n">head</span><span class="p">,</span> <span class="n">key</span><span class="p">))</span> <span class="p">{</span>
                <span class="k">return</span> <span class="n">FALSE</span><span class="p">;</span>
            <span class="p">}</span>
            <span class="kt">node_t</span> <span class="o">*</span><span class="n">inext</span> <span class="o">=</span> <span class="n">item</span><span class="o">-&gt;</span><span class="n">next</span><span class="p">;</span>
            <span class="c1">// B. 删除item前先标记item-&gt;next，如果CAS失败，那么情况同insert一样，有其他线程在find之后</span>
            <span class="c1">//    删除了item，失败后重试</span>
            <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">CAS</span><span class="p">(</span><span class="o">&amp;</span><span class="n">item</span><span class="o">-&gt;</span><span class="n">next</span><span class="p">,</span> <span class="n">inext</span><span class="p">,</span> <span class="n">MARK</span><span class="p">(</span><span class="n">inext</span><span class="p">)))</span> <span class="p">{</span>
                <span class="k">continue</span><span class="p">;</span>
            <span class="p">}</span>
            <span class="c1">// C. 对同一个元素item删除时，只会有一个线程成功走到这里</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">CAS</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pred</span><span class="o">-&gt;</span><span class="n">next</span><span class="p">,</span> <span class="n">item</span><span class="p">,</span> <span class="n">STRIP_MARK</span><span class="p">(</span><span class="n">item</span><span class="o">-&gt;</span><span class="n">next</span><span class="p">)))</span> <span class="p">{</span>
                <span class="n">haz_defer_free</span><span class="p">(</span><span class="n">item</span><span class="p">);</span>
                <span class="k">return</span> <span class="n">TRUE</span><span class="p">;</span>
            <span class="p">}</span>
        <span class="p">}</span>
        <span class="k">return</span> <span class="n">FALSE</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="kt">int</span> <span class="nf">l_find</span><span class="p">(</span><span class="kt">node_t</span> <span class="o">**</span><span class="n">pred_ptr</span><span class="p">,</span> <span class="kt">node_t</span> <span class="o">**</span><span class="n">item_ptr</span><span class="p">,</span> <span class="kt">node_t</span> <span class="o">*</span><span class="n">head</span><span class="p">,</span> <span class="kt">key_t</span> <span class="n">key</span><span class="p">)</span> <span class="p">{</span>
        <span class="kt">node_t</span> <span class="o">*</span><span class="n">pred</span> <span class="o">=</span> <span class="n">head</span><span class="p">;</span>
        <span class="kt">node_t</span> <span class="o">*</span><span class="n">item</span> <span class="o">=</span> <span class="n">head</span><span class="o">-&gt;</span><span class="n">next</span><span class="p">;</span>
        <span class="kt">hazard_t</span> <span class="o">*</span><span class="n">hp1</span> <span class="o">=</span> <span class="n">haz_get</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>
        <span class="kt">hazard_t</span> <span class="o">*</span><span class="n">hp2</span> <span class="o">=</span> <span class="n">haz_get</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span>
        <span class="k">while</span> <span class="p">(</span><span class="n">item</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">haz_set_ptr</span><span class="p">(</span><span class="n">hp1</span><span class="p">,</span> <span class="n">pred</span><span class="p">);</span>
            <span class="n">haz_set_ptr</span><span class="p">(</span><span class="n">hp2</span><span class="p">,</span> <span class="n">item</span><span class="p">);</span>
            <span class="cm">/* </span>
<span class="cm">             如果已被标记，那么紧接着item可能被移除链表甚至释放，所以需要重头查找</span>
<span class="cm">            */</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">HAS_MARK</span><span class="p">(</span><span class="n">item</span><span class="o">-&gt;</span><span class="n">next</span><span class="p">))</span> <span class="p">{</span> 
                <span class="k">return</span> <span class="n">l_find</span><span class="p">(</span><span class="n">pred_ptr</span><span class="p">,</span> <span class="n">item_ptr</span><span class="p">,</span> <span class="n">head</span><span class="p">,</span> <span class="n">key</span><span class="p">);</span>
            <span class="p">}</span>
            <span class="kt">int</span> <span class="n">d</span> <span class="o">=</span> <span class="n">KEY_CMP</span><span class="p">(</span><span class="n">item</span><span class="o">-&gt;</span><span class="n">key</span><span class="p">,</span> <span class="n">key</span><span class="p">);</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">d</span> <span class="o">&gt;=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
                <span class="o">*</span><span class="n">pred_ptr</span> <span class="o">=</span> <span class="n">pred</span><span class="p">;</span>
                <span class="o">*</span><span class="n">item_ptr</span> <span class="o">=</span> <span class="n">item</span><span class="p">;</span>
                <span class="k">return</span> <span class="n">d</span> <span class="o">==</span> <span class="mi">0</span> <span class="o">?</span> <span class="nl">TRUE</span> <span class="p">:</span> <span class="n">FALSE</span><span class="p">;</span>
            <span class="p">}</span>
            <span class="n">pred</span> <span class="o">=</span> <span class="n">item</span><span class="p">;</span>
            <span class="n">item</span> <span class="o">=</span> <span class="n">item</span><span class="o">-&gt;</span><span class="n">next</span><span class="p">;</span>
        <span class="p">}</span> 
        <span class="o">*</span><span class="n">pred_ptr</span> <span class="o">=</span> <span class="n">pred</span><span class="p">;</span>
        <span class="o">*</span><span class="n">item_ptr</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
        <span class="k">return</span> <span class="n">FALSE</span><span class="p">;</span>
    <span class="p">}</span></code></pre></div>


<p><code>haz_get</code>、<code>haz_set_ptr</code>之类的函数是一个hazard pointer实现，用于支持多线程下内存的GC。上面的代码中，要删除一个元素<code>item</code>时，会标记<code>item-&gt;next</code>，从而使得<code>insert</code>时中那个<code>CAS</code>不需要做任何调整。总结下这里的线程竞争情况：</p>

<ul>
<li>
<code>insert</code>中<code>find</code>到正常的<code>pred</code>及<code>item</code>，<code>pred-&gt;next == item</code>，然后在<code>CAS</code>前有线程删除了<code>pred</code>，此时<code>pred-&gt;next == MARK(item)</code>，<code>CAS</code>失败，重试；删除分为2种情况：a) 从链表移除，得到标记，<code>pred</code>可继续访问；b) <code>pred</code>可能被释放内存，此时再使用<code>pred</code>会错误。为了处理情况b，所以引入了类似hazard pointer的机制，可以有效保障任意一个指针<code>p</code>只要还有线程在使用它，它的内存就不会被真正释放</li>
<li>
<code>insert</code>中有多个线程在<code>pred</code>后插入元素，此时同样由<code>insert</code>中的<code>CAS</code>保证，这个不多说</li>
<li>
<code>remove</code>中情况同<code>insert</code>，<code>find</code>拿到了有效的<code>pred</code>和<code>next</code>，但在<code>CAS</code>的时候<code>pred</code>被其他线程删除，此时情况同<code>insert</code>，<code>CAS</code>失败，重试</li>
<li>任何时候改变链表结构时，无论是<code>remove</code>还是<code>insert</code>，都需要重试该操作</li>
<li>
<code>find</code>中遍历时，可能会遇到被标记删除的<code>item</code>，此时<code>item</code>根据<code>remove</code>的实现很可能被删除，所以需要重头开始遍历</li>
</ul>


<h2>ABA问题</h2>

<p>ABA问题还是存在的，<code>insert</code>中：</p>

<div class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="k">if</span> <span class="p">(</span><span class="n">CAS</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pred</span><span class="o">-&gt;</span><span class="n">next</span><span class="p">,</span> <span class="n">item</span><span class="p">,</span> <span class="n">new_item</span><span class="p">))</span> <span class="p">{</span>
        <span class="k">return</span> <span class="n">TRUE</span><span class="p">;</span>
    <span class="p">}</span></code></pre></div>


<p>如果<code>CAS</code>之前，<code>pred</code>后的<code>item</code>被移除，又以相同的地址值加进来，但其value变了，此时<code>CAS</code>会成功，但链表可能就不是有序的了。<code>pred-&gt;val &lt; new_item-&gt;val &gt; item-&gt;val</code></p>

<p>为了解决这个问题，可以利用指针值地址对齐的其他位来存储一个计数，用于表示<code>pred-&gt;next</code>的改变次数。当<code>insert</code>拿到<code>pred</code>时，<code>pred-&gt;next</code>中存储的计数假设是0，<code>CAS</code>之前其他线程移除了<code>pred-&gt;next</code>又新增回了<code>item</code>，此时<code>pred-&gt;next</code>中的计数增加，从而导致<code>insert</code>中<code>CAS</code>失败。</p>

<div class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="c1">// 最低位留作删除标志</span>
    <span class="cp">#define MASK ((sizeof(node_t) - 1) &amp; ~0x01)</span>

    <span class="cp">#define GET_TAG(p) ((markable_t)p &amp; MASK)</span>
    <span class="cp">#define TAG(p, tag) ((markable_t)p | (tag))</span>
    <span class="cp">#define MARK(p) ((markable_t)p | 0x01)</span>
    <span class="cp">#define HAS_MARK(p) ((markable_t)p &amp; 0x01)</span>
    <span class="cp">#define STRIP_MARK(p) ((node_t*)((markable_t)p &amp; ~(MASK | 0x01)))</span></code></pre></div>


<p><code>remove</code>的实现：</p>

<div class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="cm">/* 先标记再删除 */</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">CAS</span><span class="p">(</span><span class="o">&amp;</span><span class="n">sitem</span><span class="o">-&gt;</span><span class="n">next</span><span class="p">,</span> <span class="n">inext</span><span class="p">,</span> <span class="n">MARK</span><span class="p">(</span><span class="n">inext</span><span class="p">)))</span> <span class="p">{</span>
        <span class="k">continue</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="kt">int</span> <span class="n">tag</span> <span class="o">=</span> <span class="n">GET_TAG</span><span class="p">(</span><span class="n">pred</span><span class="o">-&gt;</span><span class="n">next</span><span class="p">)</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">CAS</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pred</span><span class="o">-&gt;</span><span class="n">next</span><span class="p">,</span> <span class="n">item</span><span class="p">,</span> <span class="n">TAG</span><span class="p">(</span><span class="n">STRIP_MARK</span><span class="p">(</span><span class="n">sitem</span><span class="o">-&gt;</span><span class="n">next</span><span class="p">),</span> <span class="n">tag</span><span class="p">)))</span> <span class="p">{</span>
        <span class="n">haz_defer_free</span><span class="p">(</span><span class="n">sitem</span><span class="p">);</span>
        <span class="k">return</span> <span class="n">TRUE</span><span class="p">;</span>
    <span class="p">}</span></code></pre></div>


<p><code>insert</code>中也可以更新<code>pred-&gt;next</code>的计数。</p>

<h2>总结</h2>

<p>无锁的实现，本质上都会依赖于<code>CAS</code>的互斥性。从头实现一个lock free的数据结构，可以深刻感受到lock free实现的tricky。最终代码可以从<a href="https://github.com/kevinlynx/lockfree-list">这里github</a>获取。代码中为了简单，实现了一个不是很强大的hazard pointer，可以<a href="http://codemacro.com/2015/05/03/hazard-pointer/">参考之前的博文</a>。</p>

<p class="post-footer">
            原文地址：
            <a href="http://codemacro.com/2015/05/05/lock_free_list/">http://codemacro.com/2015/05/05/lock_free_list/</a><br />
            written by <a href="http://codemacro.com">Kevin Lynx</a>
            &nbsp;posted at <a href="http://codemacro.com">http://codemacro.com</a>
            </p>

</div><img src ="http://www.cppblog.com/kevinlynx/aggbug/210555.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/kevinlynx/" target="_blank">Kevin Lynx</a> 2015-05-05 19:47 <a href="http://www.cppblog.com/kevinlynx/archive/2015/05/05/210555.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>并行编程中的内存回收Hazard Pointer</title><link>http://www.cppblog.com/kevinlynx/archive/2015/05/03/210532.html</link><dc:creator>Kevin Lynx</dc:creator><author>Kevin Lynx</author><pubDate>Sun, 03 May 2015 12:46:00 GMT</pubDate><guid>http://www.cppblog.com/kevinlynx/archive/2015/05/03/210532.html</guid><wfw:comment>http://www.cppblog.com/kevinlynx/comments/210532.html</wfw:comment><comments>http://www.cppblog.com/kevinlynx/archive/2015/05/03/210532.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/kevinlynx/comments/commentRss/210532.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/kevinlynx/services/trackbacks/210532.html</trackback:ping><description><![CDATA[<div class="entry-content">
<p>接上篇<a href="http://codemacro.com/2015/04/19/rw_thread_gc/">使用RCU技术实现读写线程无锁</a>，在没有GC机制的语言中，要实现Lock free的算法，就免不了要自己处理内存回收的问题。</p>

<p>Hazard Pointer是另一种处理这个问题的算法，而且相比起来不但简单，功能也很强大。<a href="http://blog.csdn.net/pongba/article/details/589864">锁无关的数据结构与Hazard指针</a>中讲得很好，<a href="http://en.wikipedia.org/wiki/Hazard_pointer">Wikipedia Hazard pointer</a>也描述得比较清楚，所以我这里就不讲那么细了。</p>

<p>一个简单的实现可以参考<a href="https://github.com/kevinlynx/lockfree-list/blob/master/haz_ptr.c">我的github haz_ptr.c</a></p>

<h2>原理</h2>

<p>基本原理无非也是读线程对指针进行标识，指针(指向的内存)要释放时都会缓存起来延迟到确认没有读线程了才对其真正释放。</p>

<p><code>&lt;Lock-Free Data Structures with Hazard Pointers&gt;</code>中的描述：</p>

<blockquote><p>Each reader thread owns a single-writer/multi-reader shared pointer called &#8220;hazard pointer.&#8221; When a reader thread assigns the address of a map to its hazard pointer, it is basically announcing to other threads (writers), &#8220;I am reading this map. You can replace it if you want, but don&#8217;t change its contents and certainly keep your deleteing hands off it.&#8221;</p></blockquote>

<p>关键的结构包括：<code>Hazard pointer</code>、<code>Thread Free list</code></p>

<p><code>Hazard pointer</code>：一个读线程要使用一个指针时，就会创建一个Hazard pointer包装这个指针。一个Hazard pointer会被一个线程写，多个线程读。</p>

<!-- more -->




<div class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="k">struct</span> <span class="n">HazardPointer</span> <span class="p">{</span>
        <span class="kt">void</span> <span class="o">*</span><span class="n">real_ptr</span><span class="p">;</span> <span class="c1">// 包装的指针</span>
        <span class="p">...</span> <span class="c1">// 不同的实现有不同的成员</span>
    <span class="p">};</span>

    <span class="kt">void</span> <span class="nf">func</span><span class="p">()</span> <span class="p">{</span>
        <span class="n">HazardPointer</span> <span class="o">*</span><span class="n">hp</span> <span class="o">=</span> <span class="n">accquire</span><span class="p">(</span><span class="n">_real_ptr</span><span class="p">);</span>
        <span class="p">...</span> <span class="c1">// use _real_ptr</span>
        <span class="n">release</span><span class="p">(</span><span class="n">hp</span><span class="p">);</span>
    <span class="p">}</span></code></pre></div>


<p><code>Thread Free List</code>：每个线程都有一个这样的列表，保存着将要释放的指针列表，这个列表仅对应的线程读写</p>

<div class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="kt">void</span> <span class="nf">defer_free</span><span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="n">ptr</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">_free_list</span><span class="p">.</span><span class="n">push_back</span><span class="p">(</span><span class="n">ptr</span><span class="p">);</span>
    <span class="p">}</span></code></pre></div>


<p>当某个线程要尝试释放Free List中的指针时，例如指针<code>ptr</code>，就检查所有其他线程使用的Hazard pointer，检查是否存在包装了<code>ptr</code>的Hazard pointer，如果没有则说明没有读线程正在使用<code>ptr</code>，可以安全释放<code>ptr</code>。</p>

<div class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="kt">void</span> <span class="nf">gc</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">for</span><span class="p">(</span><span class="n">ptr</span> <span class="n">in</span> <span class="n">_free_list</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">conflict</span> <span class="o">=</span> <span class="nb">false</span>
            <span class="k">for</span> <span class="p">(</span><span class="n">hp</span> <span class="n">in</span> <span class="n">_all_hazard_pointers</span><span class="p">)</span> <span class="p">{</span>
                <span class="k">if</span> <span class="p">(</span><span class="n">hp</span><span class="o">-&gt;</span><span class="n">_real_ptr</span> <span class="o">==</span> <span class="n">ptr</span><span class="p">)</span> <span class="p">{</span>
                    <span class="n">confilict</span> <span class="o">=</span> <span class="nb">true</span>
                    <span class="k">break</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">conflict</span><span class="p">)</span>
                <span class="k">delete</span> <span class="n">ptr</span>
        <span class="p">}</span>
    <span class="p">}</span></code></pre></div>


<p>以上，其实就是<code>Hazard Pointer</code>的主要内容。</p>

<h2>Hazard Pointer的管理</h2>

<p>上面的代码中没有提到<code>_all_hazard_pointers</code>及<code>accquire</code>的具体实现，这就是Hazard Pointer的管理问题。</p>

<p>《锁无关的数据结构与Hazard指针》文中创建了一个Lock free的链表来表示这个全局的Hazard Pointer List。每个Hazard Pointer有一个成员标识其是否可用。这个List中也就保存了已经被使用的Hazard Pointer集合和未被使用的Hazard Pointer集合，当所有Hazard Pointer都被使用时，就会新分配一个加进这个List。当读线程不使用指针时，需要归还Hazard Pointer，直接设置可用成员标识即可。要<code>gc()</code>时，就直接遍历这个List。</p>

<p>要实现一个Lock free的链表，并且仅需要实现头插入，还是非常简单的。本身Hazard Pointer标识某个指针时，都是用了后立即标识，所以这个实现直接支持了动态线程，支持线程的挂起等。</p>

<p>在<a href="https://code.google.com/p/nbds/">nbds</a>项目中也有一个Hazard Pointer的实现，相对要弱一点。它为每个线程都设置了自己的Hazard Pointer池，写线程要释放指针时，就访问所有其他线程的Hazard Pointer池。</p>

<div class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="k">typedef</span> <span class="k">struct</span> <span class="n">haz_local</span> <span class="p">{</span>
        <span class="c1">// Free List</span>
        <span class="kt">pending_t</span> <span class="o">*</span><span class="n">pending</span><span class="p">;</span> <span class="c1">// to be freed</span>
        <span class="kt">int</span> <span class="n">pending_size</span><span class="p">;</span>
        <span class="kt">int</span> <span class="n">pending_count</span><span class="p">;</span>

        <span class="c1">// Hazard Pointer 池，动态和静态两种</span>
        <span class="kt">haz_t</span> <span class="n">static_haz</span><span class="p">[</span><span class="n">STATIC_HAZ_PER_THREAD</span><span class="p">];</span>

        <span class="kt">haz_t</span> <span class="o">**</span><span class="n">dynamic</span><span class="p">;</span>
        <span class="kt">int</span> <span class="n">dynamic_size</span><span class="p">;</span>
        <span class="kt">int</span> <span class="n">dynamic_count</span><span class="p">;</span>

    <span class="p">}</span> <span class="n">__attribute__</span> <span class="p">((</span><span class="n">aligned</span><span class="p">(</span><span class="n">CACHE_LINE_SIZE</span><span class="p">)))</span> <span class="kt">haz_local_t</span><span class="p">;</span>

    <span class="k">static</span> <span class="kt">haz_local_t</span> <span class="n">haz_local_</span><span class="p">[</span><span class="n">MAX_NUM_THREADS</span><span class="p">]</span> <span class="o">=</span> <span class="p">{};</span></code></pre></div>


<p>每个线程当然就涉及到<code>haz_local_</code>索引(ID)的分配，就像<a href="http://codemacro.com/2015/04/19/rw_thread_gc/">使用RCU技术实现读写线程无锁</a>中的一样。这个实现为了支持线程动态创建，就需要一套线程ID的重用机制，相对复杂多了。</p>

<h2>附录</h2>

<p>最后，附上一些并行编程中的一些概念。</p>

<h3>Lock Free &amp; Wait Free</h3>

<p>常常看到<code>Lock Free</code>和<code>Wait Free</code>的概念，这些概念用于衡量一个系统或者说一段代码的并行级别，并行级别可参考<a href="http://www.cnblogs.com/jiayy/p/3246167.html">并行编程&#8212;&#8212;并发级别</a>。总之Wait Free是一个比Lock Free更牛逼的级别。</p>

<p>我自己的理解，例如《锁无关的数据结构与Hazard指针》中实现的Hazard Pointer链表就可以说是Lock Free的，注意它在插入新元素到链表头时，因为使用<code>CAS</code>，总免不了一个busy loop，有这个特征的情况下就算是<code>Lock Free</code>，虽然没锁，但某个线程的执行情况也受其他线程的影响。</p>

<p>相对而言，<code>Wait Free</code>则是每个线程的执行都是独立的，例如《锁无关的数据结构与Hazard指针》中的<code>Scan</code>函数。<code>&#8220;每个线程的执行时间都不依赖于其它任何线程的行为&#8221;</code></p>

<blockquote><p>锁无关(Lock-Free)意味着系统中总存在某个线程能够得以继续执行；而等待无关(Wait-Free)则是一个更强的条件，它意味着所有线程都能往下进行。</p></blockquote>

<h3>ABA问题</h3>

<p>在实现<code>Lock Free</code>算法的过程中，总是要使用<code>CAS</code>原语的，而<code>CAS</code>就会带来<code>ABA</code>问题。</p>

<blockquote><p>在进行CAS操作的时候，因为在更改V之前，CAS主要询问&#8220;V的值是否仍然为A&#8221;，所以在第一次读取V之后以及对V执行CAS操作之前，如果将值从A改为B，然后再改回A，会使基于CAS的算法混乱。在这种情况下，CAS操作会成功。这类问题称为ABA问题。</p></blockquote>

<p><a href="http://en.wikipedia.org/wiki/Hazard_pointer">Wiki Hazard Pointer</a>提到了一个ABA问题的好例子：在一个Lock free的栈实现中，现在要出栈，栈里的元素是<code>[A, B, C]</code>，<code>head</code>指向栈顶，那么就有<code>compare_and_swap(target=&amp;head, newvalue=B, expected=A)</code>。但是在这个操作中，其他线程把<code>A</code> <code>B</code>都出栈，且删除了<code>B</code>，又把<code>A</code>压入栈中，即<code>[A, C]</code>。那么前一个线程的<code>compare_and_swap</code>能够成功，此时<code>head</code>指向了一个已经被删除的<code>B</code>。stackoverflow上也有个例子 <a href="http://stackoverflow.com/questions/14535948/real-world-examples-for-aba-in-multithreading">Real-world examples for ABA in multithreading</a></p>

<blockquote><p>对于CAS产生的这个ABA问题，通常的解决方案是采用CAS的一个变种DCAS。DCAS，是对于每一个V增加一个引用的表示修改次数的标记符。对于每个V，如果引用修改了一次，这个计数器就加1。然后再这个变量需要update的时候，就同时检查变量的值和计数器的值。</p></blockquote>

<p>但也早有人提出<code>DCAS</code>也不是<a href="http://people.csail.mit.edu/shanir/publications/DCAS.pdf">ABA problem 的银弹</a>。</p>

<p class="post-footer">
            原文地址：
            <a href="http://codemacro.com/2015/05/03/hazard-pointer/">http://codemacro.com/2015/05/03/hazard-pointer/</a><br />
            written by <a href="http://codemacro.com">Kevin Lynx</a>
            &nbsp;posted at <a href="http://codemacro.com">http://codemacro.com</a>
            </p>

</div><img src ="http://www.cppblog.com/kevinlynx/aggbug/210532.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/kevinlynx/" target="_blank">Kevin Lynx</a> 2015-05-03 20:46 <a href="http://www.cppblog.com/kevinlynx/archive/2015/05/03/210532.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>使用RCU技术实现读写线程无锁</title><link>http://www.cppblog.com/kevinlynx/archive/2015/04/19/210386.html</link><dc:creator>Kevin Lynx</dc:creator><author>Kevin Lynx</author><pubDate>Sun, 19 Apr 2015 11:10:00 GMT</pubDate><guid>http://www.cppblog.com/kevinlynx/archive/2015/04/19/210386.html</guid><wfw:comment>http://www.cppblog.com/kevinlynx/comments/210386.html</wfw:comment><comments>http://www.cppblog.com/kevinlynx/archive/2015/04/19/210386.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://www.cppblog.com/kevinlynx/comments/commentRss/210386.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/kevinlynx/services/trackbacks/210386.html</trackback:ping><description><![CDATA[<div class="entry-content">
<p>在一个系统中有一个写线程和若干个读线程，读写线程通过一个指针共用了一个数据结构，写线程改写这个结构，读线程读取该结构。在写线程改写这个数据结构的过程中，加锁情况下读线程由于等待锁耗时会增加。</p>

<p>可以利用RCU (Read Copy Update <a href="http://www.rdrop.com/~paulmck/RCU/whatisRCU.html">What is rcu</a>)的思想来去除这个锁。本文提到的主要实现代码：<a href="https://gist.github.com/kevinlynx/ba728f2f1b33c763a6c3">gist</a></p>

<h2>RCU</h2>

<p>RCU可以说是一种替代读写锁的方法。其基于一个事实：当写线程在改变一个指针时，读线程获取这个指针，要么获取到老的值，要么获取到新的值。RCU的基本思想其实很简单，参考<a href="http://www.rdrop.com/~paulmck/RCU/whatisRCU.html">What is RCU</a>中Toy implementation可以很容易理解。一种简单的RCU流程可以描述为：</p>

<p>写线程：</p>

<pre><code>old_ptr = _ptr
tmp_ptr = copy(_ptr)     // copy
change(tmp_ptr)          // change 
_ptr = tmp_ptr           // update
synchroize(tmp_ptr)
</code></pre>

<p>写线程要更新<code>_ptr</code>指向的内容时，先复制一份新的，基于新的进行改变，更新<code>_ptr</code>指针，最后同步释放老的内存。</p>

<!-- more -->


<p>读线程：</p>

<pre><code>tmp_ptr = _ptr
use(tmp_ptr)
dereference(tmp_ptr)
</code></pre>

<p>读线程直接使用<code>_ptr</code>，使用完后需要告诉写线程自己不再使用<code>_ptr</code>。读线程获取<code>_ptr</code>时，可能会获取到老的也可能获取到新的，无论哪种RCU都需要保证这块内存是有效的。重点在<code>synchroize</code>和<code>dereference</code>。<code>synchroize</code>会等待所有使用老的<code>_ptr</code>的线程<code>dereference</code>，对于新的<code>_ptr</code>使用者其不需要等待。这个问题说白了就是写线程如何知道<code>old_ptr</code>没有任何读线程在使用，可以安全地释放。</p>

<p>这个问题实际上在<code>wait-free</code>的各种实现中有好些解法，<a href="http://stackoverflow.com/questions/22263874/how-when-to-release-memory-in-wait-free-algorithms">how-when-to-release-memory-in-wait-free-algorithms</a>这里有人总结了几种方法，例如<code>Hazard pointers</code>、<code>Quiescence period based reclamation</code>。</p>

<p>简单地使用引用计数智能指针是无法解决这个问题的，因为智能指针自己不是线程安全的，例如：</p>

<pre><code>tmp_ptr = _ptr      // 1
tmp_ptr-&gt;addRef()   // 2
use
tmp_ptr-&gt;release()
</code></pre>

<p>代码1/2行不是原子的，所以当取得<code>tmp_ptr</code>准备<code>addRef</code>时，<code>tmp_ptr</code>可能刚好被释放了。</p>

<p><code>Quiescence period based reclamation</code>方法指的是读线程需要声明自己处于<code>Quiescence period</code>，也就是不使用<code>_ptr</code>的时候，当其使用<code>_ptr</code>的时候实际是进入了一个逻辑上的临界区，当所有读线程都不再使用<code>_ptr</code>的时候，写线程就可以对内存进行安全地释放。</p>

<p>本文正是描述了一种<code>Quiescence period based reclamation</code>实现。这个实现可以用于有一个写线程和多个读线程共用若干个数据的场景。</p>

<h2>实现</h2>

<p>该方法本质上把数据同步分解为基本的内存单元读写。使用方式上可描述为：</p>

<p>读线程：</p>

<pre><code>tmp_ptr = _ptr
use
update() // 标识自己不再使用任何共享数据
</code></pre>

<p>写线程：</p>

<pre><code>old_ptr = _ptr
tmp_ptr = copy(_ptr)
change(tmp_ptr)
_ptr = tmp_ptr
gc()
defer_free(old_ptr)
</code></pre>

<p>以下具体描述读写线程的实现。</p>

<h3>写线程</h3>

<p>写线程负责标识内存需要被释放，以及检查何时可以真正释放内存。其维护了一个释放内存队列：</p>

<div class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="kt">void</span> <span class="o">*</span><span class="n">_pending</span><span class="p">[</span><span class="mi">8</span><span class="p">]</span>
    <span class="kt">uint64_t</span> <span class="n">_head</span><span class="p">,</span> <span class="n">_tail</span>

    <span class="kt">void</span> <span class="n">defer_free</span><span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="n">p</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">_head</span> <span class="o">++</span>
        <span class="n">_pending</span><span class="p">[</span><span class="n">PENDING_POS</span><span class="p">(</span><span class="n">_head</span><span class="p">)]</span> <span class="o">=</span> <span class="n">p</span>
    <span class="p">}</span>

    <span class="n">gc</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">for</span> <span class="p">(</span><span class="n">_tail</span> <span class="o">-&gt;</span> <span class="n">find_free_pos</span><span class="p">())</span>
            <span class="n">free</span><span class="p">(</span><span class="n">_pending</span><span class="p">[</span><span class="n">_tail</span><span class="p">])</span>
    <span class="p">}</span></code></pre></div>


<p><code>find_free_pos</code>找到一个可释放内存位置，在<code>[_tail, find_free_pos())</code>这个区间内所有内存是可以安全被释放的。</p>

<p>队列位置<code>_head/_tail</code>一直增大，<code>PENDING_POS</code>就是对这个位置取模，限定在队列大小范围内也是可行的，无论哪种方式，<code>_head</code>从逻辑上说一直<code>&gt;=_tail</code>，但在实际中可能小于<code>_tail</code>，所以实现时不使用大小判定，而是：</p>

<div class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="n">gc</span><span class="p">()</span> <span class="p">{</span>
        <span class="n">pos</span> <span class="o">=</span> <span class="n">find_free_pos</span><span class="p">()</span>
        <span class="k">while</span> <span class="p">(</span><span class="n">_tail</span> <span class="o">!=</span> <span class="n">pos</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">free</span><span class="p">(</span><span class="n">_pending</span><span class="p">[</span><span class="n">PENDING_POS</span><span class="p">(</span><span class="n">_tail</span><span class="p">)])</span>
            <span class="n">_tail</span> <span class="o">++</span>
        <span class="p">}</span>
    <span class="p">}</span></code></pre></div>


<h3>读线程</h3>

<p>读线程不再使用共享内存时，就标识自己：</p>

<div class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="n">update</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">static</span> <span class="n">__thread</span> <span class="kt">int</span> <span class="n">tid</span>
        <span class="n">_tmark</span><span class="p">[</span><span class="n">tid</span><span class="p">]</span> <span class="o">=</span> <span class="n">_head</span>
    <span class="p">}</span></code></pre></div>


<p>读线程的状态会影响写线程的回收逻辑，其状态分为：</p>

<ul>
<li>初始</li>
<li>活跃，会调用到<code>update</code>
</li>
<li>暂停，其他地方同步，或被挂起</li>
<li>退出</li>
</ul>


<p>读线程处于活跃状态时，它会不断地更新自己可释放内存位置(<code>_tmark[tid]</code>)。写线程检查所有读线程的<code>_tmark[tid]</code>，<code>[_tail, min(_tmark[]))</code>是所有读线程都不再使用的内存区间，可以被安全释放。</p>

<div class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="n">find_free_pos</span><span class="p">()</span> <span class="p">{</span>
        <span class="n">min</span> <span class="o">=</span> <span class="n">MAX_INTEGER</span>
        <span class="n">pos</span> <span class="o">=</span> <span class="mi">0</span>
        <span class="k">for</span> <span class="p">(</span><span class="n">tid</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">tid</span> <span class="o">&lt;</span> <span class="n">max_threads</span><span class="p">;</span> <span class="o">++</span><span class="n">tid</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">tpos</span> <span class="o">=</span> <span class="n">_tmark</span><span class="p">[</span><span class="n">tid</span><span class="p">]</span>
            <span class="n">offset</span> <span class="o">=</span> <span class="n">tpos</span> <span class="o">-</span> <span class="n">tail</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">offset</span> <span class="o">&lt;</span> <span class="n">min</span><span class="p">)</span> <span class="p">{</span>
                <span class="n">min</span> <span class="o">=</span> <span class="n">offset</span>
                <span class="n">pos</span> <span class="o">=</span> <span class="n">tpos</span>
            <span class="p">}</span>
        <span class="p">}</span>
        <span class="k">return</span> <span class="n">pos</span>
    <span class="p">}</span></code></pre></div>


<p>当读线程暂停时，其<code>_tmark[tid]</code>可能会在很长一段时间里得不到更新，此时会阻碍写线程释放内存。所以需要方法来标识读线程是否进入暂停状态。通过设置一个上次释放内存位置<code>_tfreeds[tid]</code>，标识每个线程当前内存释放到的位置。如果一个线程处于暂停状态了，那么在一定时间后，<code>_tfreeds[tid] == _tmark[tid]</code>。在查找可释放位置时，就需要忽略暂停状态的读线程：</p>

<div class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="n">find_free_pos</span><span class="p">()</span> <span class="p">{</span>
        <span class="n">min</span> <span class="o">=</span> <span class="n">MAX_INTEGER</span>
        <span class="n">pos</span> <span class="o">=</span> <span class="n">_head</span>
        <span class="k">for</span> <span class="p">(</span><span class="n">tid</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">tid</span> <span class="o">&lt;</span> <span class="n">max_threads</span><span class="p">;</span> <span class="o">++</span><span class="n">tid</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">tpos</span> <span class="o">=</span> <span class="n">_tmark</span><span class="p">[</span><span class="n">tid</span><span class="p">]</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">tpos</span> <span class="o">==</span> <span class="n">_tfreeds</span><span class="p">[</span><span class="n">tid</span><span class="p">])</span> <span class="k">continue</span>
            <span class="n">offset</span> <span class="o">=</span> <span class="n">tpos</span> <span class="o">-</span> <span class="n">tail</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">offset</span> <span class="o">&lt;</span> <span class="n">min</span><span class="p">)</span> <span class="p">{</span>
                <span class="n">min</span> <span class="o">=</span> <span class="n">offset</span>
                <span class="n">pos</span> <span class="o">=</span> <span class="n">tpos</span>
            <span class="p">}</span>
        <span class="p">}</span>
        <span class="k">for</span> <span class="p">(</span><span class="n">tid</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">tid</span> <span class="o">&lt;</span> <span class="n">max_threads</span><span class="p">;</span> <span class="o">++</span><span class="n">tid</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">_tfreeds</span><span class="p">[</span><span class="n">tid</span><span class="p">]</span> <span class="o">!=</span> <span class="n">_tmark</span><span class="p">[</span><span class="n">tid</span><span class="p">])</span> 
                <span class="n">_tfreeds</span><span class="p">[</span><span class="n">tid</span><span class="p">]</span> <span class="o">=</span> <span class="n">pos</span>
        <span class="p">}</span>
        <span class="k">return</span> <span class="n">pos</span>
    <span class="p">}</span></code></pre></div>


<p>但是当所有线程都处于暂停状态时，写线程可能还在工作，上面的实现就会返回<code>_head</code>，此时写线程依然可以正常释放内存。</p>

<p><strong>小结</strong>，该方法原理可用下图表示：</p>

<p><img src="http://codemacro.com/assets/res/rw_thread.png" alt="" /></p>

<h3>线程动态增加/减少</h3>

<p>如果读线程可能中途退出，中途动态增加，那么<code>_tmark[]</code>就需要被复用，此时线程<code>tid</code>的分配调整为动态的即可：</p>

<div class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="k">class</span> <span class="nc">ThreadIdPool</span> <span class="p">{</span>
    <span class="k">public</span><span class="o">:</span>
        <span class="c1">// 动态获取一个线程tid，某线程每次调用该接口返回相同的值</span>
        <span class="kt">int</span> <span class="n">get</span><span class="p">()</span>
        <span class="c1">// 线程退出时回收该tid</span>
        <span class="kt">void</span> <span class="n">put</span><span class="p">(</span><span class="kt">int</span> <span class="n">id</span><span class="p">)</span>
    <span class="p">}</span></code></pre></div>


<p><code>ThreadIdPool</code>的实现无非就是利用TLS，以及在线程退出时得到通知以回收tid。那么对于读线程的<code>update</code>实现变为：</p>

<div class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="n">update</span><span class="p">()</span> <span class="p">{</span>
        <span class="n">tid</span> <span class="o">=</span> <span class="n">_idPool</span><span class="o">-&gt;</span><span class="n">get</span><span class="p">()</span>
        <span class="n">_tmark</span><span class="p">[</span><span class="n">tid</span><span class="p">]</span> <span class="o">=</span> <span class="n">_head</span>
    <span class="p">}</span></code></pre></div>


<p>当某个线程退出时，<code>_tmark[tid]</code>和<code>_tfreeds[tid]</code>不需要做任何处理，当新创建的线程复用了该<code>tid</code>时，可以立即复用<code>_tmark[tid]</code>和<code>_tfreeds[tid]</code>，此时这2个值必然是相等的。</p>

<p>以上，就是整个方法的实现。</p>

<h2>线程可读可写</h2>

<p>以上方法适用场景还是不够通用。在<a href="https://code.google.com/p/nbds/">nbds</a>项目（实现了一些无锁数据结构的toy project）中有一份虽然简单但也有启发的实现(rcu.c)。该实现支持任意线程<code>defer_free</code>，所有线程<code>update</code>。<code>update</code>除了声明不再使用任何共享内存外，还可能回收内存。任意线程都可能维护一些待释放的内存，任意一块内存可能被任意其他线程使用。那么它是如何内存回收的？</p>

<p>本文描述的方法是所有读线程自己声明自己，然后由写线程主动来检查。不同于此方法， nbds的实现，基于一种<strong>通知扩散</strong>的方式。该方式以这样一种方式工作：</p>

<p>当某个线程尝试内存回收时，它需要知道所有其他线程的空闲位置（相当于<code>_tmark[tid]</code>），它通知下一个线程我需要释放的范围。当下一个线程<code>update</code>时（离开临界区），它会将上个线程的通知继续告诉下一个线程，直到最后这个通知回到发起线程。那么对于发起线程而言，这个释放请求在所有线程中走了一遍，得到了大家的认可，可以安全释放。每个线程都以这样的方式工作。</p>

<div class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="kt">void</span> <span class="nf">rcu_defer_free</span> <span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="n">x</span><span class="p">)</span> <span class="p">{</span>
        <span class="p">...</span>
        <span class="n">rcu_</span><span class="p">[</span><span class="n">next_thread_id</span><span class="p">][</span><span class="n">tid_</span><span class="p">]</span> <span class="o">=</span> <span class="n">rcu_last_posted_</span><span class="p">[</span><span class="n">tid_</span><span class="p">][</span><span class="n">tid_</span><span class="p">]</span> <span class="o">=</span> <span class="n">pending_</span><span class="p">[</span><span class="n">tid_</span><span class="p">]</span><span class="o">-&gt;</span><span class="n">head</span><span class="p">;</span>
        <span class="p">...</span>
    <span class="p">}</span>

    <span class="kt">void</span> <span class="nf">rcu_update</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
        <span class="p">...</span>
        <span class="k">for</span> <span class="p">(</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="n">num_threads_</span><span class="p">;</span> <span class="o">++</span><span class="n">i</span><span class="p">)</span> <span class="p">{</span>
            <span class="p">...</span>     
            <span class="kt">uint64_t</span> <span class="n">x</span> <span class="o">=</span> <span class="n">rcu_</span><span class="p">[</span><span class="n">tid_</span><span class="p">][</span><span class="n">i</span><span class="p">];</span> <span class="c1">// 其它线程发给自己的通知</span>
            <span class="n">rcu_</span><span class="p">[</span><span class="n">next_thread_id</span><span class="p">][</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">rcu_last_posted_</span><span class="p">[</span><span class="n">tid_</span><span class="p">][</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">x</span><span class="p">;</span> <span class="c1">// 扩散出去</span>
            <span class="p">...</span>
        <span class="p">}</span>
        <span class="p">...</span>
        <span class="k">while</span> <span class="p">(</span><span class="n">q</span><span class="o">-&gt;</span><span class="n">tail</span> <span class="o">!=</span> <span class="n">rcu_</span><span class="p">[</span><span class="n">tid_</span><span class="p">][</span><span class="n">tid_</span><span class="p">])</span> <span class="p">{</span>
            <span class="n">free</span>
        <span class="p">}</span>     
        <span class="p">...</span>
    <span class="p">}</span></code></pre></div>


<p>这个实现相对简单，不支持线程暂停，以及线程动态增加和减少。</p>

<p class="post-footer">
            原文地址：
            <a href="http://codemacro.com/2015/04/19/rw_thread_gc/">http://codemacro.com/2015/04/19/rw_thread_gc/</a><br />
            written by <a href="http://codemacro.com">Kevin Lynx</a>
            &nbsp;posted at <a href="http://codemacro.com">http://codemacro.com</a>
            </p>

</div><img src ="http://www.cppblog.com/kevinlynx/aggbug/210386.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/kevinlynx/" target="_blank">Kevin Lynx</a> 2015-04-19 19:10 <a href="http://www.cppblog.com/kevinlynx/archive/2015/04/19/210386.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>记一次tcmalloc分配内存引起的coredump</title><link>http://www.cppblog.com/kevinlynx/archive/2015/04/06/210257.html</link><dc:creator>Kevin Lynx</dc:creator><author>Kevin Lynx</author><pubDate>Mon, 06 Apr 2015 10:33:00 GMT</pubDate><guid>http://www.cppblog.com/kevinlynx/archive/2015/04/06/210257.html</guid><wfw:comment>http://www.cppblog.com/kevinlynx/comments/210257.html</wfw:comment><comments>http://www.cppblog.com/kevinlynx/archive/2015/04/06/210257.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://www.cppblog.com/kevinlynx/comments/commentRss/210257.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/kevinlynx/services/trackbacks/210257.html</trackback:ping><description><![CDATA[<div class="entry-content">
<h2>现象</h2>

<p>线上的服务出现coredump，堆栈为：</p>

<pre><code>#0  0x000000000045d145 in GetStackTrace(void**, int, int) ()
#1  0x000000000045ec22 in tcmalloc::PageHeap::GrowHeap(unsigned long) ()
#2  0x000000000045eeb3 in tcmalloc::PageHeap::New(unsigned long) ()
#3  0x0000000000459ee8 in tcmalloc::CentralFreeList::Populate() ()
#4  0x000000000045a088 in tcmalloc::CentralFreeList::FetchFromSpansSafe() ()
#5  0x000000000045a10a in tcmalloc::CentralFreeList::RemoveRange(void**, void**, int) ()
#6  0x000000000045c282 in tcmalloc::ThreadCache::FetchFromCentralCache(unsigned long, unsigned long) ()
#7  0x0000000000470766 in tc_malloc ()
#8  0x00007f75532cd4c2 in __conhash_get_rbnode (node=0x22c86870, hash=30)
        at build/release64/cm_sub/conhash/conhash_inter.c:88
#9  0x00007f75532cd76e in __conhash_add_replicas (conhash=0x24fbc7e0, iden=&lt;value optimized out&gt;)
        at build/release64/cm_sub/conhash/conhash_inter.c:45
#10 0x00007f75532cd1fa in conhash_add_node (conhash=0x24fbc7e0, iden=0) at build/release64/cm_sub/conhash/conhash.c:72
#11 0x00007f75532c651b in cm_sub::TopoCluster::initLBPolicyInfo (this=0x2593a400)
        at build/release64/cm_sub/topo_cluster.cpp:114
#12 0x00007f75532cad73 in cm_sub::TopoClusterManager::processClusterMapTable (this=0xa219e0, ref=0x267ea8c0)
        at build/release64/cm_sub/topo_cluster_manager.cpp:396
#13 0x00007f75532c5a93 in cm_sub::SubRespMsgProcess::reinitCluster (this=0x9c2f00, msg=0x4e738ed0)
        at build/release64/cm_sub/sub_resp_msg_process.cpp:157
...
</code></pre>

<p>查看了应用层相关数据结构，基本数据都是没有问题的。所以最初怀疑是tcmalloc内部维护了错误的内存，在分配内存时出错，这个堆栈只是问题的表象。几天后，线上的另一个服务，基于同样的库，也core了，堆栈还是一样的。</p>

<p>最初定位问题都是从最近更新的东西入手，包括依赖的server环境，但都没有明显的问题，所以最后只能从core的直接原因入手。</p>

<!-- more -->


<h2>分析GetStackTrace</h2>

<p>确认core的详细位置：</p>

<pre><code># core在该指令
0x000000000045d145 &lt;_Z13GetStackTracePPvii+21&gt;: mov    0x8(%rax),%r9

(gdb) p/x $rip              # core 的指令位置
$9 = 0x45d145
(gdb) p/x $rax              
$10 = 0x4e73aa58
(gdb) x/1a $rax+0x8         # rax + 8 = 0x4e73aa60
0x4e73aa60:     0x0
</code></pre>

<p>该指令尝试从[0x4e73aa60]处读取内容，然后出错，这个内存单元不可读。但是具体这个指令在代码中是什么意思，<strong>需要将这个指令对应到代码中</strong>。获取tcmalloc的源码，发现<code>GetStackTrace</code>根据编译选项有很多实现，所以这里选择最可能的实现，然后对比汇编以确认代码是否匹配。最初选择的是<code>stacktrace_x86-64-inl.h</code>，后来发现完全不匹配，又选择了<code>stacktrace_x86-inl.h</code>。这个实现版本里也有对64位平台的支持。</p>

<p><code>stacktrace_x86-inl.h</code>里使用了一些宏来生成函数名和参数，精简后代码大概为：</p>

<div class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="kt">int</span> <span class="n">GET_STACK_TRACE_OR_FRAMES</span> <span class="p">{</span>
      <span class="kt">void</span> <span class="o">**</span><span class="n">sp</span><span class="p">;</span>
      <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">rbp</span><span class="p">;</span>
      <span class="n">__asm__</span> <span class="nf">volatile</span> <span class="p">(</span><span class="s">"mov %%rbp, %0"</span> <span class="o">:</span> <span class="s">"=r"</span> <span class="p">(</span><span class="n">rbp</span><span class="p">));</span>
      <span class="n">sp</span> <span class="o">=</span> <span class="p">(</span><span class="kt">void</span> <span class="o">**</span><span class="p">)</span> <span class="n">rbp</span><span class="p">;</span>

      <span class="kt">int</span> <span class="n">n</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
      <span class="k">while</span> <span class="p">(</span><span class="n">sp</span> <span class="o">&amp;&amp;</span> <span class="n">n</span> <span class="o">&lt;</span> <span class="n">max_depth</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">*</span><span class="p">(</span><span class="n">sp</span><span class="o">+</span><span class="mi">1</span><span class="p">)</span> <span class="o">==</span> <span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="kt">void</span> <span class="o">*&gt;</span><span class="p">(</span><span class="mi">0</span><span class="p">))</span> <span class="p">{</span>
          <span class="k">break</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="kt">void</span> <span class="o">**</span><span class="n">next_sp</span> <span class="o">=</span> <span class="n">NextStackFrame</span><span class="o">&lt;!</span><span class="n">IS_STACK_FRAMES</span><span class="p">,</span> <span class="n">IS_WITH_CONTEXT</span><span class="o">&gt;</span><span class="p">(</span><span class="n">sp</span><span class="p">,</span> <span class="n">ucp</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">skip_count</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
          <span class="n">skip_count</span><span class="o">--</span><span class="p">;</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
          <span class="n">result</span><span class="p">[</span><span class="n">n</span><span class="p">]</span> <span class="o">=</span> <span class="o">*</span><span class="p">(</span><span class="n">sp</span><span class="o">+</span><span class="mi">1</span><span class="p">);</span>
          <span class="n">n</span><span class="o">++</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="n">sp</span> <span class="o">=</span> <span class="n">next_sp</span><span class="p">;</span>
      <span class="p">}</span>
      <span class="k">return</span> <span class="n">n</span><span class="p">;</span>
    <span class="p">}</span></code></pre></div>


<p><code>NextStackFrame</code>是一个模板函数，包含一大堆代码，精简后非常简单：</p>

<div class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="k">template</span><span class="o">&lt;</span><span class="kt">bool</span> <span class="n">STRICT_UNWINDING</span><span class="p">,</span> <span class="kt">bool</span> <span class="n">WITH_CONTEXT</span><span class="o">&gt;</span>
    <span class="k">static</span> <span class="kt">void</span> <span class="o">**</span><span class="n">NextStackFrame</span><span class="p">(</span><span class="kt">void</span> <span class="o">**</span><span class="n">old_sp</span><span class="p">,</span> <span class="k">const</span> <span class="kt">void</span> <span class="o">*</span><span class="n">uc</span><span class="p">)</span> <span class="p">{</span>
      <span class="kt">void</span> <span class="o">**</span><span class="n">new_sp</span> <span class="o">=</span> <span class="p">(</span><span class="kt">void</span> <span class="o">**</span><span class="p">)</span> <span class="o">*</span><span class="n">old_sp</span><span class="p">;</span>
      <span class="k">if</span> <span class="p">(</span><span class="n">STRICT_UNWINDING</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">new_sp</span> <span class="o">&lt;=</span> <span class="n">old_sp</span><span class="p">)</span> <span class="k">return</span> <span class="nb">NULL</span><span class="p">;</span>
        <span class="k">if</span> <span class="p">((</span><span class="kt">uintptr_t</span><span class="p">)</span><span class="n">new_sp</span> <span class="o">-</span> <span class="p">(</span><span class="kt">uintptr_t</span><span class="p">)</span><span class="n">old_sp</span> <span class="o">&gt;</span> <span class="mi">100000</span><span class="p">)</span> <span class="k">return</span> <span class="nb">NULL</span><span class="p">;</span>
      <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">new_sp</span> <span class="o">==</span> <span class="n">old_sp</span><span class="p">)</span> <span class="k">return</span> <span class="nb">NULL</span><span class="p">;</span>
        <span class="k">if</span> <span class="p">((</span><span class="n">new_sp</span> <span class="o">&gt;</span> <span class="n">old_sp</span><span class="p">)</span>
            <span class="o">&amp;&amp;</span> <span class="p">((</span><span class="kt">uintptr_t</span><span class="p">)</span><span class="n">new_sp</span> <span class="o">-</span> <span class="p">(</span><span class="kt">uintptr_t</span><span class="p">)</span><span class="n">old_sp</span> <span class="o">&gt;</span> <span class="mi">1000000</span><span class="p">))</span> <span class="k">return</span> <span class="nb">NULL</span><span class="p">;</span>
      <span class="p">}</span>
      <span class="k">if</span> <span class="p">((</span><span class="kt">uintptr_t</span><span class="p">)</span><span class="n">new_sp</span> <span class="o">&amp;</span> <span class="p">(</span><span class="k">sizeof</span><span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="p">)</span> <span class="o">-</span> <span class="mi">1</span><span class="p">))</span> <span class="k">return</span> <span class="nb">NULL</span><span class="p">;</span>

      <span class="k">return</span> <span class="n">new_sp</span><span class="p">;</span>
    <span class="p">}</span></code></pre></div>


<p>上面这个代码到汇编的对比过程还是花了些时间，其中汇编中出现的一些常量可以大大缩短对比时间，例如上面出现了<code>100000</code>，汇编中就有：</p>

<pre><code>0x000000000045d176 &lt;_Z13GetStackTracePPvii+70&gt;: cmp    $0x186a0,%rbx  # 100000=0x186a0
</code></pre>

<p><em>注意<code>NextStackFrame</code>中的 <code>if (STRICT_UNWINDING)</code>使用的是模板参数，这导致生成的代码中根本没有else部分，也就没有<code>1000000</code>这个常量</em></p>

<p>在对比代码的过程中，可以<strong>知道关键的几个寄存器、内存位置对应到代码中的变量，从而可以还原core时的现场环境</strong>。分析过程中不一定要从第一行汇编读，可以从较明显的位置读，从而还原整个代码，<strong>函数返回指令、跳转指令、比较指令、读内存指令、参数寄存器</strong>等都是比较明显对应的地方。</p>

<p>另外注意<code>GetStackTrace</code>在<code>RecordGrowth</code>中调用，传入了3个参数：</p>

<pre><code>GetStackTrace(t-&gt;stack, kMaxStackDepth-1, 3); // kMaxStackDepth = 31
</code></pre>

<p>以下是我分析的简单注解：</p>

<pre><code>(gdb) disassemble
Dump of assembler code for function _Z13GetStackTracePPvii:
0x000000000045d130 &lt;_Z13GetStackTracePPvii+0&gt;:  push   %rbp
0x000000000045d131 &lt;_Z13GetStackTracePPvii+1&gt;:  mov    %rsp,%rbp
0x000000000045d134 &lt;_Z13GetStackTracePPvii+4&gt;:  push   %rbx
0x000000000045d135 &lt;_Z13GetStackTracePPvii+5&gt;:  mov    %rbp,%rax
0x000000000045d138 &lt;_Z13GetStackTracePPvii+8&gt;:  xor    %r8d,%r8d
0x000000000045d13b &lt;_Z13GetStackTracePPvii+11&gt;: test   %rax,%rax
0x000000000045d13e &lt;_Z13GetStackTracePPvii+14&gt;: je     0x45d167 &lt;_Z13GetStackTracePPvii+55&gt;
0x000000000045d140 &lt;_Z13GetStackTracePPvii+16&gt;: cmp    %esi,%r8d        # while ( .. max_depth &gt; n ?
0x000000000045d143 &lt;_Z13GetStackTracePPvii+19&gt;: jge    0x45d167 &lt;_Z13GetStackTracePPvii+55&gt;
0x000000000045d145 &lt;_Z13GetStackTracePPvii+21&gt;: mov    0x8(%rax),%r9    # 关键位置：*(sp+1) -&gt; r9, rax 对应 sp变量
0x000000000045d149 &lt;_Z13GetStackTracePPvii+25&gt;: test   %r9,%r9          # *(sp+1) == 0 ?
0x000000000045d14c &lt;_Z13GetStackTracePPvii+28&gt;: je     0x45d167 &lt;_Z13GetStackTracePPvii+55&gt;
0x000000000045d14e &lt;_Z13GetStackTracePPvii+30&gt;: mov    (%rax),%rcx      # new_sp = *old_sp，这里已经是NextStackFrame的代码
0x000000000045d151 &lt;_Z13GetStackTracePPvii+33&gt;: cmp    %rcx,%rax        # new_sp &lt;= old_sp ? 
0x000000000045d154 &lt;_Z13GetStackTracePPvii+36&gt;: jb     0x45d170 &lt;_Z13GetStackTracePPvii+64&gt;  # new_sp &gt; old_sp 跳转
0x000000000045d156 &lt;_Z13GetStackTracePPvii+38&gt;: xor    %ecx,%ecx
0x000000000045d158 &lt;_Z13GetStackTracePPvii+40&gt;: test   %edx,%edx        # skip_count &gt; 0 ?
0x000000000045d15a &lt;_Z13GetStackTracePPvii+42&gt;: jle    0x45d186 &lt;_Z13GetStackTracePPvii+86&gt;
0x000000000045d15c &lt;_Z13GetStackTracePPvii+44&gt;: sub    $0x1,%edx        # skip_count--
0x000000000045d15f &lt;_Z13GetStackTracePPvii+47&gt;: mov    %rcx,%rax        
0x000000000045d162 &lt;_Z13GetStackTracePPvii+50&gt;: test   %rax,%rax        # while (sp ?
0x000000000045d165 &lt;_Z13GetStackTracePPvii+53&gt;: jne    0x45d140 &lt;_Z13GetStackTracePPvii+16&gt;
0x000000000045d167 &lt;_Z13GetStackTracePPvii+55&gt;: pop    %rbx
0x000000000045d168 &lt;_Z13GetStackTracePPvii+56&gt;: leaveq 
0x000000000045d169 &lt;_Z13GetStackTracePPvii+57&gt;: mov    %r8d,%eax        # r8 存储了返回值，r8=n
0x000000000045d16c &lt;_Z13GetStackTracePPvii+60&gt;: retq                    # return n
0x000000000045d16d &lt;_Z13GetStackTracePPvii+61&gt;: nopl   (%rax)
0x000000000045d170 &lt;_Z13GetStackTracePPvii+64&gt;: mov    %rcx,%rbx        
0x000000000045d173 &lt;_Z13GetStackTracePPvii+67&gt;: sub    %rax,%rbx        # offset = new_sp - old_sp
0x000000000045d176 &lt;_Z13GetStackTracePPvii+70&gt;: cmp    $0x186a0,%rbx    # offset &gt; 100000 ?
0x000000000045d17d &lt;_Z13GetStackTracePPvii+77&gt;: ja     0x45d156 &lt;_Z13GetStackTracePPvii+38&gt; # return NULL
0x000000000045d17f &lt;_Z13GetStackTracePPvii+79&gt;: test   $0x7,%cl         # new_sp &amp; (sizeof(void*) - 1)
0x000000000045d182 &lt;_Z13GetStackTracePPvii+82&gt;: je     0x45d158 &lt;_Z13GetStackTracePPvii+40&gt;
0x000000000045d184 &lt;_Z13GetStackTracePPvii+84&gt;: jmp    0x45d156 &lt;_Z13GetStackTracePPvii+38&gt;
0x000000000045d186 &lt;_Z13GetStackTracePPvii+86&gt;: movslq %r8d,%rax        # rax = n
0x000000000045d189 &lt;_Z13GetStackTracePPvii+89&gt;: add    $0x1,%r8d        # n++
0x000000000045d18d &lt;_Z13GetStackTracePPvii+93&gt;: mov    %r9,(%rdi,%rax,8)# 关键位置：result[n] = *(sp+1)
0x000000000045d191 &lt;_Z13GetStackTracePPvii+97&gt;: jmp    0x45d15f &lt;_Z13GetStackTracePPvii+47&gt;
</code></pre>

<p>分析过程比较耗时，同时还可以分析下<code>GetStackTrace</code>函数的实现原理，其实就是利用RBP寄存器不断回溯，从而得到整个调用堆栈各个函数的地址（严格来说是返回地址）。简单示意下函数调用中RBP的情况：</p>

<pre><code>   ...
saved registers          # i.e push rbx
local variabes           # i.e sub 0x10, rsp
return address           # call xxx
last func RBP            # push rbp; mov rsp, rbp
saved registers
local variables 
return address
last func RBP
...                      # rsp
</code></pre>

<p>总之，<strong>一般情况下，任何一个函数中，RBP寄存器指向了当前函数的栈基址，该栈基址中又存储了调用者的栈基址，同时该栈基址前面还存储了调用者的返回地址</strong>。所以，<code>GetStackTrace</code>的实现，简单来说大概就是：</p>

<div class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="n">sp</span> <span class="o">=</span> <span class="n">rbp</span>  <span class="c1">// 取得当前函数GetStackTrace的栈基址</span>
    <span class="k">while</span> <span class="p">(</span><span class="n">n</span> <span class="o">&lt;</span> <span class="n">max_depth</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">new_sp</span> <span class="o">=</span> <span class="o">*</span><span class="n">sp</span>
        <span class="n">result</span><span class="p">[</span><span class="n">n</span><span class="p">]</span> <span class="o">=</span> <span class="o">*</span><span class="p">(</span><span class="n">new_sp</span><span class="o">+</span><span class="mi">1</span><span class="p">)</span>
        <span class="n">n</span><span class="o">++</span>
    <span class="p">}</span></code></pre></div>


<p>以上，最终就知道了以下关键信息：</p>

<ul>
<li>r8 对应变量 n，表示当前取到第几个栈帧了</li>
<li>rax 对应变量 sp，代码core在 *(sp+1)</li>
<li>rdi 对应变量 result，用于存储取得的各个地址</li>
</ul>


<p>然后可以看看现场是怎样的：</p>

<pre><code>(gdb) x/10a $rdi
0x1ffc9b98:     0x45a088 &lt;_ZN8tcmalloc15CentralFreeList18FetchFromSpansSafeEv+40&gt;       0x45a10a &lt;_ZN8tcmalloc15CentralFreeList11RemoveRangeEPPvS2_i+106&gt;
0x1ffc9ba8:     0x45c282 &lt;_ZN8tcmalloc11ThreadCache21FetchFromCentralCacheEmm+114&gt;      0x470766 &lt;tc_malloc+790&gt;
0x1ffc9bb8:     0x7f75532cd4c2 &lt;__conhash_get_rbnode+34&gt;        0x0
0x1ffc9bc8:     0x0     0x0
0x1ffc9bd8:     0x0     0x0

(gdb) p/x $r8
$3 = 0x5

(gdb) p/x $rax
$4 = 0x4e73aa58
</code></pre>

<p><strong>小结：</strong></p>

<p><code>GetStackTrace</code>在取调用<code>__conhash_get_rbnode</code>的函数时出错，取得了5个函数地址。当前使用的RBP为<code>0x4e73aa58</code>。</p>

<h2>错误的RBP</h2>

<p>RBP也是从堆栈中取出来的，既然这个地址有问题，首先想到的就是有代码局部变量/数组写越界。例如<code>sprintf</code>的使用。而且，<strong>一般写越界破坏堆栈，都可能是把调用者的堆栈破坏了</strong>，例如：</p>

<pre><code>char s[32];
memcpy(s, p, 1024);
</code></pre>

<p>因为写入都是从低地址往高地址写，而调用者的堆栈在高地址。当然，也会遇到写坏调用者的调用者的堆栈，也就是跨栈帧越界写，例如以前遇到的：</p>

<pre><code>len = vsnprintf(buf, sizeof(buf), fmt, wtf-long-string);
buf[len] = 0;
</code></pre>

<p><code>__conhash_get_rbnode</code>的RBP是在tcmalloc的堆栈中取的：</p>

<pre><code>(gdb) f 7
#7  0x0000000000470766 in tc_malloc ()
(gdb) x/10a $rsp
0x4e738b80:     0x4e73aa58      0x22c86870
0x4e738b90:     0x4e738bd0      0x85
0x4e738ba0:     0x4e73aa58      0x7f75532cd4c2 &lt;__conhash_get_rbnode+34&gt;   # 0x4e73aa58
</code></pre>

<p>所以这里就会怀疑是<code>tcmalloc</code>这个函数里有把堆栈破坏，这个时候就是读代码，看看有没有疑似危险的地方，未果。这里就陷入了僵局，怀疑又遇到了跨栈帧破坏的情况，这个时候就只能<code>__conhash_get_rbnode</code>调用栈中周围的函数翻翻，例如调用<code>__conhash_get_rbnode</code>的函数<code>__conhash_add_replicas</code>中恰好有字符串操作：</p>

<div class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="kt">void</span> <span class="nf">__conhash_add_replicas</span><span class="p">(</span><span class="kt">conhash_t</span> <span class="o">*</span><span class="n">conhash</span><span class="p">,</span> <span class="kt">int32_t</span> <span class="n">iden</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="kt">node_t</span><span class="o">*</span> <span class="n">node</span> <span class="o">=</span> <span class="n">__conhash_create_node</span><span class="p">(</span><span class="n">iden</span><span class="p">,</span> <span class="n">conhash</span><span class="o">-&gt;</span><span class="n">replica</span><span class="p">);</span>
        <span class="p">...</span>
        <span class="kt">char</span> <span class="n">buf</span><span class="p">[</span><span class="n">buf_len</span><span class="p">];</span> <span class="c1">// buf_len = 64</span>
        <span class="p">...</span>
        <span class="n">snprintf</span><span class="p">(</span><span class="n">buf</span><span class="p">,</span> <span class="n">buf_len</span><span class="p">,</span> <span class="n">VIRT_NODE_HASH_FMT</span><span class="p">,</span> <span class="n">node</span><span class="o">-&gt;</span><span class="n">iden</span><span class="p">,</span> <span class="n">i</span><span class="p">);</span>
        <span class="kt">uint32_t</span> <span class="n">hash</span> <span class="o">=</span> <span class="n">conhash</span><span class="o">-&gt;</span><span class="n">cb_hashfunc</span><span class="p">(</span><span class="n">buf</span><span class="p">);</span>
        <span class="k">if</span><span class="p">(</span><span class="n">util_rbtree_search</span><span class="p">(</span><span class="o">&amp;</span><span class="p">(</span><span class="n">conhash</span><span class="o">-&gt;</span><span class="n">vnode_tree</span><span class="p">),</span> <span class="n">hash</span><span class="p">)</span> <span class="o">==</span> <span class="nb">NULL</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="kt">util_rbtree_node_t</span><span class="o">*</span> <span class="n">rbnode</span> <span class="o">=</span> <span class="n">__conhash_get_rbnode</span><span class="p">(</span><span class="n">node</span><span class="p">,</span> <span class="n">hash</span><span class="p">);</span>
            <span class="p">...</span></code></pre></div>


<p>这段代码最终发现是没有问题的，这里又耗费了不少时间。后来发现若干个函数里的RBP都有点奇怪，这个调用栈比较正常的范围是：0x4e738c90</p>

<pre><code>(gdb) f 8
#8  0x00007f75532cd4c2 in __conhash_get_rbnode (node=0x22c86870, hash=30)
(gdb) p/x $rbp
$6 = 0x4e73aa58     # 这个还不算特别可疑
(gdb) f 9
#9  0x00007f75532cd76e in __conhash_add_replicas (conhash=0x24fbc7e0, iden=&lt;value optimized out&gt;)
(gdb) p/x $rbp
$7 = 0x4e738c60     # 这个也不算特别可疑
(gdb) f 10
#10 0x00007f75532cd1fa in conhash_add_node (conhash=0x24fbc7e0, iden=0) at build/release64/cm_sub/conhash/conhash.c:72
(gdb) p/x $rbp      # 可疑
$8 = 0x0
(gdb) f 11
#11 0x00007f75532c651b in cm_sub::TopoCluster::initLBPolicyInfo (this=0x2593a400)
(gdb) p/x $rbp      # 可疑
$9 = 0x2598fef0
</code></pre>

<p><strong>为什么很多函数中RBP都看起来不正常？</strong> 想了想真要是代码里把堆栈破坏了，这错误得发生得多巧妙？</p>

<h2>错误RBP的来源</h2>

<p>然后转机来了，脑海中突然闪出<code>-fomit-frame-pointer</code>。编译器生成的代码中是可以不需要栈基址指针的，也就是RBP寄存器不作为栈基址寄存器。大部分函数或者说开启了<code>frame-pointer</code>的函数，其函数头都会有以下指令：</p>

<pre><code>push   %rbp
mov    %rsp,%rbp
...
</code></pre>

<p>表示保存调用者的栈基址到栈中，以及设置自己的栈基址。看下<code>__conhash</code>系列函数；</p>

<pre><code>Dump of assembler code for function __conhash_get_rbnode:
0x00007f75532cd4a0 &lt;__conhash_get_rbnode+0&gt;:    mov    %rbx,-0x18(%rsp)
0x00007f75532cd4a5 &lt;__conhash_get_rbnode+5&gt;:    mov    %rbp,-0x10(%rsp)
...
</code></pre>

<p>这个库是单独编译的，没有显示指定<code>-fno-omit-frame-pointer</code>，查阅<a href="https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html">gcc手册</a>，o2优化是开启了<code>omit-frame-pinter</code> 的。</p>

<p>在没有RBP的情况下，tcmalloc的<code>GetStackTrace</code>尝试读RBP取获取调用返回地址，自然是有问题的。但是，<strong>如果整个调用栈中的函数，要么有RBP，要么没有RBP，那么<code>GetStackTrace</code>取出的结果最多就是跳过一些栈帧，不会出错。</strong> 除非，这中间的某个函数把RBP寄存器另作他用（编译器省出这个寄存器肯定是要另作他用的）。所以这里继续追查这个错误地址<code>0x4e73aa58</code>的来源。</p>

<p>来源已经比较明显，肯定是<code>__conhash_get_rbnode</code>中设置的，因为这个函数的RBP是在被调用者<code>tcmalloc</code>中保存的。</p>

<pre><code>Dump of assembler code for function __conhash_get_rbnode:
0x00007f75532cd4a0 &lt;__conhash_get_rbnode+0&gt;:    mov    %rbx,-0x18(%rsp)
0x00007f75532cd4a5 &lt;__conhash_get_rbnode+5&gt;:    mov    %rbp,-0x10(%rsp)
0x00007f75532cd4aa &lt;__conhash_get_rbnode+10&gt;:   mov    %esi,%ebp                    # 改写了RBP
0x00007f75532cd4ac &lt;__conhash_get_rbnode+12&gt;:   mov    %r12,-0x8(%rsp)
0x00007f75532cd4b1 &lt;__conhash_get_rbnode+17&gt;:   sub    $0x18,%rsp
0x00007f75532cd4b5 &lt;__conhash_get_rbnode+21&gt;:   mov    %rdi,%r12
0x00007f75532cd4b8 &lt;__conhash_get_rbnode+24&gt;:   mov    $0x30,%edi
0x00007f75532cd4bd &lt;__conhash_get_rbnode+29&gt;:   callq  0x7f75532b98c8 &lt;malloc@plt&gt;  # 调用tcmalloc，汇编到这里即可
</code></pre>

<p>这里打印RSI寄存器的值可能会被误导，因为任何时候打印寄存器的值可能都是错的，除非它有被显示保存。不过这里可以看出RSI的值来源于参数(RSI对应第二个参数)：</p>

<div class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="kt">void</span> <span class="nf">__conhash_add_replicas</span><span class="p">(</span><span class="kt">conhash_t</span> <span class="o">*</span><span class="n">conhash</span><span class="p">,</span> <span class="kt">int32_t</span> <span class="n">iden</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="kt">node_t</span><span class="o">*</span> <span class="n">node</span> <span class="o">=</span> <span class="n">__conhash_create_node</span><span class="p">(</span><span class="n">iden</span><span class="p">,</span> <span class="n">conhash</span><span class="o">-&gt;</span><span class="n">replica</span><span class="p">);</span>
        <span class="p">...</span>
        <span class="kt">char</span> <span class="n">buf</span><span class="p">[</span><span class="n">buf_len</span><span class="p">];</span> <span class="c1">// buf_len = 64</span>
        <span class="p">...</span>
        <span class="n">snprintf</span><span class="p">(</span><span class="n">buf</span><span class="p">,</span> <span class="n">buf_len</span><span class="p">,</span> <span class="n">VIRT_NODE_HASH_FMT</span><span class="p">,</span> <span class="n">node</span><span class="o">-&gt;</span><span class="n">iden</span><span class="p">,</span> <span class="n">i</span><span class="p">);</span>
        <span class="kt">uint32_t</span> <span class="n">hash</span> <span class="o">=</span> <span class="n">conhash</span><span class="o">-&gt;</span><span class="n">cb_hashfunc</span><span class="p">(</span><span class="n">buf</span><span class="p">);</span> <span class="c1">// hash值由一个字符串哈希函数计算</span>
        <span class="k">if</span><span class="p">(</span><span class="n">util_rbtree_search</span><span class="p">(</span><span class="o">&amp;</span><span class="p">(</span><span class="n">conhash</span><span class="o">-&gt;</span><span class="n">vnode_tree</span><span class="p">),</span> <span class="n">hash</span><span class="p">)</span> <span class="o">==</span> <span class="nb">NULL</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="kt">util_rbtree_node_t</span><span class="o">*</span> <span class="n">rbnode</span> <span class="o">=</span> <span class="n">__conhash_get_rbnode</span><span class="p">(</span><span class="n">node</span><span class="p">,</span> <span class="n">hash</span><span class="p">);</span>  <span class="c1">// hash值</span>
            <span class="p">...</span></code></pre></div>


<p>追到<code>__conhash_add_replicas</code>：</p>

<pre><code>0x00007f75532cd764 &lt;__conhash_add_replicas+164&gt;:        mov    %ebx,%esi    # 来源于rbx
0x00007f75532cd766 &lt;__conhash_add_replicas+166&gt;:        mov    %r15,%rdi
0x00007f75532cd769 &lt;__conhash_add_replicas+169&gt;:        callq  0x7f75532b9e48 &lt;__conhash_get_rbnode@plt&gt;

(gdb) p/x $rbx
$11 = 0x4e73aa58
(gdb) p/x hash
$12 = 0x4e73aa58      # 0x4e73aa58
</code></pre>

<p>找到了<code>0x4e73aa58</code>的来源。这个地址值竟然是一个字符串哈希算法算出来的！这里还可以看看这个字符串的内容：</p>

<pre><code>(gdb) x/1s $rsp
0x4e738bd0:      "conhash-00000-00133"
</code></pre>

<p>这个碉堡的哈希函数是<code>conhash_hash_def</code>。</p>

<h2>coredump的条件</h2>

<p>以上，既然只要某个库<code>omit-frame-pointer</code>，那tcmalloc就可能出错，为什么发生的频率并不高呢？这个可以回到<code>GetStackTrace</code>尤其是<code>NextStackFrame</code>的实现，其中包含了几个合法RBP的判定：</p>

<div class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="k">if</span> <span class="p">(</span><span class="n">new_sp</span> <span class="o">&lt;=</span> <span class="n">old_sp</span><span class="p">)</span> <span class="k">return</span> <span class="nb">NULL</span><span class="p">;</span>  <span class="c1">// 上一个栈帧的RBP肯定比当前的大</span>
        <span class="k">if</span> <span class="p">((</span><span class="kt">uintptr_t</span><span class="p">)</span><span class="n">new_sp</span> <span class="o">-</span> <span class="p">(</span><span class="kt">uintptr_t</span><span class="p">)</span><span class="n">old_sp</span> <span class="o">&gt;</span> <span class="mi">100000</span><span class="p">)</span> <span class="k">return</span> <span class="nb">NULL</span><span class="p">;</span> <span class="c1">// 指针值范围还必须在100000内</span>
        <span class="p">...</span>
    <span class="k">if</span> <span class="p">((</span><span class="kt">uintptr_t</span><span class="p">)</span><span class="n">new_sp</span> <span class="o">&amp;</span> <span class="p">(</span><span class="k">sizeof</span><span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="p">)</span> <span class="o">-</span> <span class="mi">1</span><span class="p">))</span> <span class="k">return</span> <span class="nb">NULL</span><span class="p">;</span> <span class="c1">// 由于本身保存的是指针，所以还必须是sizeof(void*)的整数倍，对齐</span></code></pre></div>


<p>有了以上条件，才使得这个core几率变得很低。</p>

<h2>总结</h2>

<p>最后，如果你很熟悉tcmalloc，整个问题估计就被秒解了：<a href="http://gperftools.googlecode.com/svn/trunk/INSTALL">tcmalloc INSTALL</a></p>

<h2>附</h2>

<p>另外附上另一个有意思的东西。</p>

<p>在分析<code>__conhash_add_replicas</code>时，其内定义了一个64字节的字符数组，查看其堆栈：</p>

<pre><code>(gdb) x/20a $rsp
0x4e738bd0:     0x2d687361686e6f63      0x30302d3030303030          # 这些是字符串conhash-00000-00133
0x4e738be0:     0x333331        0x0
0x4e738bf0:     0x0     0x7f75532cd69e &lt;__conhash_create_node+78&gt;
0x4e738c00:     0x24fbc7e0      0x4e738c60
0x4e738c10:     0x24fbc7e0      0x7f75532cd6e3 &lt;__conhash_add_replicas+35&gt;
0x4e738c20:     0x0     0x24fbc7e8
0x4e738c30:     0x4e738c20      0x24fbc7e0
0x4e738c40:     0x22324360      0x246632c0
0x4e738c50:     0x0     0x0
0x4e738c60:     0x0     0x7f75532cd1fa &lt;conhash_add_node+74&gt;
</code></pre>

<p>最开始我觉得<code>buf</code>占64字节，也就是整个[0x4e738bd0, 0x4e738c10)内存，但是这块内存里居然有函数地址，这一度使我怀疑这里有问题。后来醒悟这些地址是定义<code>buf</code>前调用<code>__conhash_create_node</code>产生的，调用过程中写到堆栈里，调用完后栈指针改变，但并不需要清空栈中的内容。</p>

<p class="post-footer">
            原文地址：
            <a href="http://codemacro.com/2015/04/06/tcmalloc-getstacktrace/">http://codemacro.com/2015/04/06/tcmalloc-getstacktrace/</a><br />
            written by <a href="http://codemacro.com">Kevin Lynx</a>
            &nbsp;posted at <a href="http://codemacro.com">http://codemacro.com</a>
            </p>

</div><img src ="http://www.cppblog.com/kevinlynx/aggbug/210257.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/kevinlynx/" target="_blank">Kevin Lynx</a> 2015-04-06 18:33 <a href="http://www.cppblog.com/kevinlynx/archive/2015/04/06/210257.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>基于内存查看STL常用容器内容</title><link>http://www.cppblog.com/kevinlynx/archive/2014/12/03/209016.html</link><dc:creator>Kevin Lynx</dc:creator><author>Kevin Lynx</author><pubDate>Wed, 03 Dec 2014 14:08:00 GMT</pubDate><guid>http://www.cppblog.com/kevinlynx/archive/2014/12/03/209016.html</guid><wfw:comment>http://www.cppblog.com/kevinlynx/comments/209016.html</wfw:comment><comments>http://www.cppblog.com/kevinlynx/archive/2014/12/03/209016.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://www.cppblog.com/kevinlynx/comments/commentRss/209016.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/kevinlynx/services/trackbacks/209016.html</trackback:ping><description><![CDATA[<div class="entry-content">
<p>有时候在线上使用gdb调试程序core问题时，可能没有符号文件，拿到的仅是一个内存地址，如果这个指向的是一个STL对象，那么如何查看这个对象的内容呢？</p>

<p>只需要知道STL各个容器的数据结构实现，就可以查看其内容。本文描述了SGI STL实现中常用容器的数据结构，以及如何在gdb中查看其内容。</p>

<h2>string</h2>

<p>string，即<code>basic_string</code> <code>bits/basic_string.h</code>：</p>

<div class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="k">mutable</span> <span class="n">_Alloc_hider</span>  <span class="n">_M_dataplus</span><span class="p">;</span>
    <span class="p">...</span> 
      <span class="k">const</span> <span class="n">_CharT</span><span class="o">*</span>
      <span class="n">c_str</span><span class="p">()</span> <span class="k">const</span>
      <span class="p">{</span> <span class="k">return</span> <span class="n">_M_data</span><span class="p">();</span> <span class="p">}</span>
    <span class="p">...</span>    
      <span class="n">_CharT</span><span class="o">*</span>
      <span class="n">_M_data</span><span class="p">()</span> <span class="k">const</span> 
      <span class="p">{</span> <span class="k">return</span>  <span class="n">_M_dataplus</span><span class="p">.</span><span class="n">_M_p</span><span class="p">;</span> <span class="p">}</span>

    <span class="p">...</span>
      <span class="k">struct</span> <span class="nl">_Alloc_hider</span> <span class="p">:</span> <span class="n">_Alloc</span>
      <span class="p">{</span>
    <span class="n">_Alloc_hider</span><span class="p">(</span><span class="n">_CharT</span><span class="o">*</span> <span class="n">__dat</span><span class="p">,</span> <span class="k">const</span> <span class="n">_Alloc</span><span class="o">&amp;</span> <span class="n">__a</span><span class="p">)</span>
    <span class="o">:</span> <span class="n">_Alloc</span><span class="p">(</span><span class="n">__a</span><span class="p">),</span> <span class="n">_M_p</span><span class="p">(</span><span class="n">__dat</span><span class="p">)</span> <span class="p">{</span> <span class="p">}</span>

    <span class="n">_CharT</span><span class="o">*</span> <span class="n">_M_p</span><span class="p">;</span> <span class="c1">// The actual data.</span>
      <span class="p">};</span>
   
      <span class="n">size_type</span>
      <span class="n">length</span><span class="p">()</span> <span class="k">const</span>
      <span class="p">{</span> <span class="k">return</span> <span class="n">_M_rep</span><span class="p">()</span><span class="o">-&gt;</span><span class="n">_M_length</span><span class="p">;</span> <span class="p">}</span>

      <span class="n">_Rep</span><span class="o">*</span>
      <span class="n">_M_rep</span><span class="p">()</span> <span class="k">const</span>
      <span class="p">{</span> <span class="k">return</span> <span class="o">&amp;</span><span class="p">((</span><span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="n">_Rep</span><span class="o">*&gt;</span> <span class="p">(</span><span class="n">_M_data</span><span class="p">()))[</span><span class="o">-</span><span class="mi">1</span><span class="p">]);</span> <span class="p">}</span>

      <span class="p">...</span>
       <span class="k">struct</span> <span class="n">_Rep_base</span>
      <span class="p">{</span>
    <span class="n">size_type</span>       <span class="n">_M_length</span><span class="p">;</span>
    <span class="n">size_type</span>       <span class="n">_M_capacity</span><span class="p">;</span>
    <span class="n">_Atomic_word</span>        <span class="n">_M_refcount</span><span class="p">;</span>
      <span class="p">};</span>

      <span class="k">struct</span> <span class="nl">_Rep</span> <span class="p">:</span> <span class="n">_Rep_base</span></code></pre></div>


<p>即，string内有一个指针，指向实际的字符串位置，这个位置前面有一个<code>_Rep</code>结构，其内保存了字符串的长度、可用内存以及引用计数。当我们拿到一个string对象的地址时，可以通过以下代码获取相关值：</p>

<!-- more -->




<div class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="kt">void</span> <span class="nf">ds_str_i</span><span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="n">p</span><span class="p">)</span> <span class="p">{</span>
        <span class="kt">char</span> <span class="o">**</span><span class="n">raw</span> <span class="o">=</span> <span class="p">(</span><span class="kt">char</span><span class="o">**</span><span class="p">)</span><span class="n">p</span><span class="p">;</span>
        <span class="kt">char</span> <span class="o">*</span><span class="n">s</span> <span class="o">=</span> <span class="o">*</span><span class="n">raw</span><span class="p">;</span>
        <span class="kt">size_t</span> <span class="n">len</span> <span class="o">=</span> <span class="o">*</span><span class="p">(</span><span class="kt">size_t</span><span class="o">*</span><span class="p">)(</span><span class="n">s</span> <span class="o">-</span> <span class="k">sizeof</span><span class="p">(</span><span class="kt">size_t</span><span class="p">)</span> <span class="o">*</span> <span class="mi">3</span><span class="p">);</span>
        <span class="n">printf</span><span class="p">(</span><span class="s">"str: %s (%zd)</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">s</span><span class="p">,</span> <span class="n">len</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="kt">size_t</span> <span class="nf">ds_str</span><span class="p">()</span> <span class="p">{</span>
        <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">s</span> <span class="o">=</span> <span class="s">"hello"</span><span class="p">;</span>
        <span class="n">ds_str_i</span><span class="p">(</span><span class="o">&amp;</span><span class="n">s</span><span class="p">);</span>
        <span class="k">return</span> <span class="n">s</span><span class="p">.</span><span class="n">size</span><span class="p">();</span>
    <span class="p">}</span></code></pre></div>


<p>在gdb中拿到一个string的地址时，可以以下打印出该字符串及长度：</p>

<pre><code>(gdb) x/1a p
0x7fffffffe3a0: 0x606028
(gdb) p (char*)0x606028
$2 = 0x606028 "hello"
(gdb) x/1dg 0x606028-24
0x606010:       5
</code></pre>

<h2>vector</h2>

<p>众所周知vector实现就是一块连续的内存，<code>bits/stl_vector.h</code>。</p>

<div class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="k">template</span><span class="o">&lt;</span><span class="k">typename</span> <span class="n">_Tp</span><span class="p">,</span> <span class="k">typename</span> <span class="n">_Alloc</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">allocator</span><span class="o">&lt;</span><span class="n">_Tp</span><span class="o">&gt;</span> <span class="o">&gt;</span>
    <span class="k">class</span> <span class="nc">vector</span> <span class="o">:</span> <span class="k">protected</span> <span class="n">_Vector_base</span><span class="o">&lt;</span><span class="n">_Tp</span><span class="p">,</span> <span class="n">_Alloc</span><span class="o">&gt;</span>

    <span class="p">...</span>
    <span class="k">template</span><span class="o">&lt;</span><span class="k">typename</span> <span class="n">_Tp</span><span class="p">,</span> <span class="k">typename</span> <span class="n">_Alloc</span><span class="o">&gt;</span>
    <span class="k">struct</span> <span class="n">_Vector_base</span>
    <span class="p">{</span>
      <span class="k">typedef</span> <span class="k">typename</span> <span class="n">_Alloc</span><span class="o">::</span><span class="k">template</span> <span class="n">rebind</span><span class="o">&lt;</span><span class="n">_Tp</span><span class="o">&gt;::</span><span class="n">other</span> <span class="n">_Tp_alloc_type</span><span class="p">;</span>

      <span class="k">struct</span> <span class="nl">_Vector_impl</span>
      <span class="p">:</span> <span class="k">public</span> <span class="n">_Tp_alloc_type</span>
      <span class="p">{</span>
    <span class="n">_Tp</span><span class="o">*</span>           <span class="n">_M_start</span><span class="p">;</span>
    <span class="n">_Tp</span><span class="o">*</span>           <span class="n">_M_finish</span><span class="p">;</span>
    <span class="n">_Tp</span><span class="o">*</span>           <span class="n">_M_end_of_storage</span><span class="p">;</span>
    <span class="n">_Vector_impl</span><span class="p">(</span><span class="n">_Tp_alloc_type</span> <span class="k">const</span><span class="o">&amp;</span> <span class="n">__a</span><span class="p">)</span>
    <span class="o">:</span> <span class="n">_Tp_alloc_type</span><span class="p">(</span><span class="n">__a</span><span class="p">),</span> <span class="n">_M_start</span><span class="p">(</span><span class="mi">0</span><span class="p">),</span> <span class="n">_M_finish</span><span class="p">(</span><span class="mi">0</span><span class="p">),</span> <span class="n">_M_end_of_storage</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
    <span class="p">{</span> <span class="p">}</span>
      <span class="p">};</span>


      <span class="n">_Vector_impl</span> <span class="n">_M_impl</span><span class="p">;</span></code></pre></div>


<p>可以看出<code>sizeof(vector&lt;xxx&gt;)=24</code>，其内也就是3个指针，<code>_M_start</code>指向首元素地址，<code>_M_finish</code>指向最后一个节点+1，<code>_M_end_of_storage</code>是可用空间最后的位置。</p>

<div class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="n">iterator</span>
      <span class="nf">end</span><span class="p">()</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="o">-&gt;</span><span class="n">_M_impl</span><span class="p">.</span><span class="n">_M_finish</span><span class="p">);</span> <span class="p">}</span>
      <span class="n">const_iterator</span>
      <span class="p">...</span>
      <span class="n">begin</span><span class="p">()</span> <span class="k">const</span>
      <span class="p">{</span> <span class="k">return</span> <span class="n">const_iterator</span> <span class="p">(</span><span class="k">this</span><span class="o">-&gt;</span><span class="n">_M_impl</span><span class="p">.</span><span class="n">_M_start</span><span class="p">);</span> <span class="p">}</span>
      <span class="p">...</span>
      <span class="n">size_type</span>
      <span class="n">capacity</span><span class="p">()</span> <span class="k">const</span>
      <span class="p">{</span> <span class="k">return</span> <span class="n">size_type</span><span class="p">(</span><span class="n">const_iterator</span><span class="p">(</span><span class="k">this</span><span class="o">-&gt;</span><span class="n">_M_impl</span><span class="p">.</span><span class="n">_M_end_of_storage</span><span class="p">)</span>
             <span class="o">-</span> <span class="n">begin</span><span class="p">());</span> <span class="p">}</span></code></pre></div>


<p>可以通过代码从一个vector对象地址输出其信息：</p>

<div class="highlight"><pre><code class="language-c--" data-lang="c++"><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="kt">void</span> <span class="n">ds_vec_i</span><span class="p">(</span><span class="kt">void</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="n">start</span> <span class="o">=</span> <span class="o">*</span><span class="p">(</span><span class="n">T</span><span class="o">**</span><span class="p">)</span><span class="n">p</span><span class="p">;</span>
        <span class="n">T</span> <span class="o">*</span><span class="n">finish</span> <span class="o">=</span> <span class="o">*</span><span class="p">(</span><span class="n">T</span><span class="o">**</span><span class="p">)((</span><span class="kt">char</span><span class="o">*</span><span class="p">)</span><span class="n">p</span> <span class="o">+</span> <span class="k">sizeof</span><span class="p">(</span><span class="kt">void</span><span class="o">*</span><span class="p">));</span>
        <span class="n">T</span> <span class="o">*</span><span class="n">end_storage</span> <span class="o">=</span> <span class="o">*</span><span class="p">(</span><span class="n">T</span><span class="o">**</span><span class="p">)((</span><span class="kt">char</span><span class="o">*</span><span class="p">)</span><span class="n">p</span> <span class="o">+</span> <span class="mi">2</span> <span class="o">*</span> <span class="k">sizeof</span><span class="p">(</span><span class="kt">void</span><span class="o">*</span><span class="p">));</span>
        <span class="n">printf</span><span class="p">(</span><span class="s">"vec size: %ld, avaiable size: %ld</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">finish</span> <span class="o">-</span> <span class="n">start</span><span class="p">,</span> <span class="n">end_storage</span> <span class="o">-</span> <span class="n">start</span><span class="p">);</span> 
    <span class="p">}</span>

    <span class="kt">size_t</span> <span class="n">ds_vec</span><span class="p">()</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="kt">int</span><span class="o">&gt;</span> <span class="n">vec</span><span class="p">;</span>
        <span class="n">vec</span><span class="p">.</span><span class="n">push_back</span><span class="p">(</span><span class="mh">0x11</span><span class="p">);</span>
        <span class="n">vec</span><span class="p">.</span><span class="n">push_back</span><span class="p">(</span><span class="mh">0x22</span><span class="p">);</span>
        <span class="n">vec</span><span class="p">.</span><span class="n">push_back</span><span class="p">(</span><span class="mh">0x33</span><span class="p">);</span>
        <span class="n">ds_vec_i</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span><span class="p">(</span><span class="o">&amp;</span><span class="n">vec</span><span class="p">);</span>
        <span class="k">return</span> <span class="n">vec</span><span class="p">.</span><span class="n">size</span><span class="p">();</span>
    <span class="p">}</span></code></pre></div>


<p>使用gdb输出一个vector中的内容：</p>

<pre><code>(gdb) p p
$3 = (void *) 0x7fffffffe380
(gdb) x/1a p
0x7fffffffe380: 0x606080
(gdb) x/3xw 0x606080
0x606080:       0x00000011      0x00000022      0x00000033
</code></pre>

<h2>list</h2>

<p>众所周知list被实现为一个链表。准确来说是一个双向链表。list本身是一个特殊节点，其代表end，其指向的下一个元素才是list真正的第一个节点：</p>

<p><code>bits/stl_list.h</code></p>

<div class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="kt">bool</span>
      <span class="n">empty</span><span class="p">()</span> <span class="k">const</span>
      <span class="p">{</span> <span class="k">return</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">_M_impl</span><span class="p">.</span><span class="n">_M_node</span><span class="p">.</span><span class="n">_M_next</span> <span class="o">==</span> <span class="o">&amp;</span><span class="k">this</span><span class="o">-&gt;</span><span class="n">_M_impl</span><span class="p">.</span><span class="n">_M_node</span><span class="p">;</span> <span class="p">}</span>

      <span class="n">const_iterator</span>
      <span class="n">begin</span><span class="p">()</span> <span class="k">const</span>
      <span class="p">{</span> <span class="k">return</span> <span class="n">const_iterator</span><span class="p">(</span><span class="k">this</span><span class="o">-&gt;</span><span class="n">_M_impl</span><span class="p">.</span><span class="n">_M_node</span><span class="p">.</span><span class="n">_M_next</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="p">{</span> <span class="k">return</span> <span class="n">iterator</span><span class="p">(</span><span class="o">&amp;</span><span class="k">this</span><span class="o">-&gt;</span><span class="n">_M_impl</span><span class="p">.</span><span class="n">_M_node</span><span class="p">);</span> <span class="p">}</span>

      <span class="p">...</span>

    <span class="k">struct</span> <span class="n">_List_node_base</span>
    <span class="p">{</span>
        <span class="n">_List_node_base</span><span class="o">*</span> <span class="n">_M_next</span><span class="p">;</span>   <span class="c1">///&lt; Self-explanatory</span>
        <span class="n">_List_node_base</span><span class="o">*</span> <span class="n">_M_prev</span><span class="p">;</span>   <span class="c1">///&lt; Self-explanatory</span>
        <span class="p">...</span>
    <span class="p">};</span>
         
    <span class="k">template</span><span class="o">&lt;</span><span class="k">typename</span> <span class="n">_Tp</span><span class="o">&gt;</span>
    <span class="k">struct</span> <span class="nl">_List_node</span> <span class="p">:</span> <span class="k">public</span> <span class="n">_List_node_base</span>
    <span class="p">{</span>
      <span class="n">_Tp</span> <span class="n">_M_data</span><span class="p">;</span>                <span class="c1">///&lt; User's data.</span>
    <span class="p">};</span>
      
    <span class="k">template</span><span class="o">&lt;</span><span class="k">typename</span> <span class="n">_Tp</span><span class="p">,</span> <span class="k">typename</span> <span class="n">_Alloc</span><span class="o">&gt;</span>
    <span class="k">class</span> <span class="nc">_List_base</span>
    <span class="p">{</span>
        <span class="p">...</span>
      <span class="k">struct</span> <span class="nl">_List_impl</span>
      <span class="p">:</span> <span class="k">public</span> <span class="n">_Node_alloc_type</span>
      <span class="p">{</span>
    <span class="n">_List_node_base</span> <span class="n">_M_node</span><span class="p">;</span>
        <span class="p">...</span>
      <span class="p">};</span>

      <span class="n">_List_impl</span> <span class="n">_M_impl</span><span class="p">;</span>

          
    <span class="k">template</span><span class="o">&lt;</span><span class="k">typename</span> <span class="n">_Tp</span><span class="p">,</span> <span class="k">typename</span> <span class="n">_Alloc</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">allocator</span><span class="o">&lt;</span><span class="n">_Tp</span><span class="o">&gt;</span> <span class="o">&gt;</span>
    <span class="k">class</span> <span class="nc">list</span> <span class="o">:</span> <span class="k">protected</span> <span class="n">_List_base</span><span class="o">&lt;</span><span class="n">_Tp</span><span class="p">,</span> <span class="n">_Alloc</span><span class="o">&gt;</span></code></pre></div>


<p>所以<code>sizeof(list&lt;xx&gt;)=16</code>，两个指针。每一个真正的节点首先是包含两个指针，然后是元素内容(<code>_List_node</code>)。</p>

<p>通过代码输出list的内容：</p>

<div class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="cp">#define NEXT(ptr, T) do { \</span>
<span class="cp">        void *n = *(char**)ptr; \</span>
<span class="cp">        T val = *(T*)((char**)ptr + 2); \</span>
<span class="cp">        printf("list item %p val: 0x%x\n", ptr, val); \</span>
<span class="cp">        ptr = n; \</span>
<span class="cp">    } while (0)</span>

    <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="kt">void</span> <span class="n">ds_list_i</span><span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="n">p</span><span class="p">)</span> <span class="p">{</span>
        <span class="kt">void</span> <span class="o">*</span><span class="n">ptr</span> <span class="o">=</span> <span class="o">*</span><span class="p">(</span><span class="kt">char</span><span class="o">**</span><span class="p">)</span><span class="n">p</span><span class="p">;</span>

        <span class="n">NEXT</span><span class="p">(</span><span class="n">ptr</span><span class="p">,</span> <span class="n">T</span><span class="p">);</span>
        <span class="n">NEXT</span><span class="p">(</span><span class="n">ptr</span><span class="p">,</span> <span class="n">T</span><span class="p">);</span>
        <span class="n">NEXT</span><span class="p">(</span><span class="n">ptr</span><span class="p">,</span> <span class="n">T</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="kt">size_t</span> <span class="n">ds_list</span><span class="p">()</span> <span class="p">{</span>
        <span class="n">std</span><span class="o">::</span><span class="n">list</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span> <span class="n">lst</span><span class="p">;</span>
        <span class="n">lst</span><span class="p">.</span><span class="n">push_back</span><span class="p">(</span><span class="mh">0x11</span><span class="p">);</span>
        <span class="n">lst</span><span class="p">.</span><span class="n">push_back</span><span class="p">(</span><span class="mh">0x22</span><span class="p">);</span>
        <span class="n">lst</span><span class="p">.</span><span class="n">push_back</span><span class="p">(</span><span class="mh">0x33</span><span class="p">);</span>
        <span class="n">ds_list_i</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span><span class="p">(</span><span class="o">&amp;</span><span class="n">lst</span><span class="p">);</span>
        <span class="k">return</span> <span class="n">lst</span><span class="p">.</span><span class="n">size</span><span class="p">();</span>
    <span class="p">}</span></code></pre></div>


<p>在gdb中可以以下方式遍历该list：</p>

<pre><code>(gdb) p p
$4 = (void *) 0x7fffffffe390
(gdb) x/1a p
0x7fffffffe390: 0x606080
(gdb) x/1xw 0x606080+16         # 元素1 
0x606090:       0x00000011
(gdb) x/1a 0x606080
0x606080:       0x6060a0
(gdb) x/1xw 0x6060a0+16         # 元素2
0x6060b0:       0x00000022
</code></pre>

<h2>map</h2>

<p>map使用的是红黑树实现，实际使用的是<code>stl_tree.h</code>实现：</p>

<p><code>bits/stl_map.h</code></p>

<div class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="k">typedef</span> <span class="n">_Rb_tree</span><span class="o">&lt;</span><span class="n">key_type</span><span class="p">,</span> <span class="n">value_type</span><span class="p">,</span> <span class="n">_Select1st</span><span class="o">&lt;</span><span class="n">value_type</span><span class="o">&gt;</span><span class="p">,</span>
               <span class="n">key_compare</span><span class="p">,</span> <span class="n">_Pair_alloc_type</span><span class="o">&gt;</span> <span class="n">_Rep_type</span><span class="p">;</span>
    <span class="p">...</span>
     <span class="n">_Rep_type</span> <span class="n">_M_t</span><span class="p">;</span>
    <span class="p">...</span> 

      <span class="n">iterator</span>
      <span class="n">begin</span><span class="p">()</span>
      <span class="p">{</span> <span class="k">return</span> <span class="n">_M_t</span><span class="p">.</span><span class="n">begin</span><span class="p">();</span> <span class="p">}</span></code></pre></div>


<p><code>bits/stl_tree.h</code></p>

<div class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="k">struct</span> <span class="n">_Rb_tree_node_base</span>
      <span class="p">{</span>
        <span class="k">typedef</span> <span class="n">_Rb_tree_node_base</span><span class="o">*</span> <span class="n">_Base_ptr</span><span class="p">;</span>
        <span class="k">typedef</span> <span class="k">const</span> <span class="n">_Rb_tree_node_base</span><span class="o">*</span> <span class="n">_Const_Base_ptr</span><span class="p">;</span>

        <span class="n">_Rb_tree_color</span>  <span class="n">_M_color</span><span class="p">;</span>
        <span class="n">_Base_ptr</span>       <span class="n">_M_parent</span><span class="p">;</span>
        <span class="n">_Base_ptr</span>       <span class="n">_M_left</span><span class="p">;</span>
        <span class="n">_Base_ptr</span>       <span class="n">_M_right</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="k">typename</span> <span class="n">_Val</span><span class="o">&gt;</span>
    <span class="k">struct</span> <span class="nl">_Rb_tree_node</span> <span class="p">:</span> <span class="k">public</span> <span class="n">_Rb_tree_node_base</span>
    <span class="p">{</span>
      <span class="k">typedef</span> <span class="n">_Rb_tree_node</span><span class="o">&lt;</span><span class="n">_Val</span><span class="o">&gt;*</span> <span class="n">_Link_type</span><span class="p">;</span>
      <span class="n">_Val</span> <span class="n">_M_value_field</span><span class="p">;</span>
    <span class="p">};</span>


    <span class="k">template</span><span class="o">&lt;</span><span class="k">typename</span> <span class="n">_Key_compare</span><span class="p">,</span>
           <span class="kt">bool</span> <span class="n">_Is_pod_comparator</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">__is_pod</span><span class="o">&lt;</span><span class="n">_Key_compare</span><span class="o">&gt;::</span><span class="n">__value</span><span class="o">&gt;</span>
        <span class="k">struct</span> <span class="nl">_Rb_tree_impl</span> <span class="p">:</span> <span class="k">public</span> <span class="n">_Node_allocator</span>
        <span class="p">{</span>
      <span class="n">_Key_compare</span>      <span class="n">_M_key_compare</span><span class="p">;</span>
      <span class="n">_Rb_tree_node_base</span>    <span class="n">_M_header</span><span class="p">;</span>
      <span class="n">size_type</span>         <span class="n">_M_node_count</span><span class="p">;</span> <span class="c1">// Keeps track of size of tree.</span>
      <span class="p">...</span>
        <span class="p">}</span>
    
    <span class="n">_Rb_tree_impl</span><span class="o">&lt;</span><span class="n">_Compare</span><span class="o">&gt;</span> <span class="n">_M_impl</span><span class="p">;</span>
    <span class="p">...</span>

      <span class="n">iterator</span>
      <span class="n">begin</span><span class="p">()</span>
      <span class="p">{</span>
    <span class="k">return</span> <span class="n">iterator</span><span class="p">(</span><span class="k">static_cast</span><span class="o">&lt;</span><span class="n">_Link_type</span><span class="o">&gt;</span>
            <span class="p">(</span><span class="k">this</span><span class="o">-&gt;</span><span class="n">_M_impl</span><span class="p">.</span><span class="n">_M_header</span><span class="p">.</span><span class="n">_M_left</span><span class="p">));</span>
      <span class="p">}</span></code></pre></div>


<p>所以可以看出，大部分时候(取决于<code>_M_key_compare</code>) <code>sizeof(map&lt;xx&gt;)=48</code>，主要的元素是：</p>

<div class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="n">_Rb_tree_color</span>  <span class="n">_M_color</span><span class="p">;</span> <span class="c1">// 节点颜色</span>
        <span class="n">_Base_ptr</span>       <span class="n">_M_parent</span><span class="p">;</span> <span class="c1">// 父节点</span>
        <span class="n">_Base_ptr</span>       <span class="n">_M_left</span><span class="p">;</span> <span class="c1">// 左节点</span>
        <span class="n">_Base_ptr</span>       <span class="n">_M_right</span><span class="p">;</span> <span class="c1">// 右节点</span>
        <span class="n">_Val</span>            <span class="n">_M_value_field</span> <span class="c1">// 同list中节点技巧一致，后面是实际的元素</span></code></pre></div>


<p>同list中的实现一致，map本身作为一个节点，其不是一个存储数据的节点，</p>

<p><code>_Rb_tree::end</code></p>

<div class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="n">iterator</span>
      <span class="nf">end</span><span class="p">()</span>
      <span class="p">{</span> <span class="k">return</span> <span class="n">iterator</span><span class="p">(</span><span class="k">static_cast</span><span class="o">&lt;</span><span class="n">_Link_type</span><span class="o">&gt;</span><span class="p">(</span><span class="o">&amp;</span><span class="k">this</span><span class="o">-&gt;</span><span class="n">_M_impl</span><span class="p">.</span><span class="n">_M_header</span><span class="p">));</span> <span class="p">}</span></code></pre></div>


<p>由于节点值在<code>_Rb_tree_node_base</code>后，所以任意时候拿到节点就可以偏移这个结构体拿到节点值，节点的值是一个pair，包含了key和value。</p>

<p>在gdb中打印以下map的内容：</p>

<div class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="kt">size_t</span> <span class="nf">ds_map</span><span class="p">()</span> <span class="p">{</span>
        <span class="n">std</span><span class="o">::</span><span class="n">map</span><span class="o">&lt;</span><span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="p">,</span> <span class="kt">int</span><span class="o">&gt;</span> <span class="n">imap</span><span class="p">;</span>
        <span class="n">imap</span><span class="p">[</span><span class="s">"abc"</span><span class="p">]</span> <span class="o">=</span> <span class="mh">0xbbb</span><span class="p">;</span>
        <span class="k">return</span> <span class="n">imap</span><span class="p">.</span><span class="n">size</span><span class="p">();</span>
    <span class="p">}</span></code></pre></div>


<pre><code>(gdb) p/x &amp;imap
$7 = 0x7fffffffe370
(gdb) x/1a (char*)&amp;imap+24       # _M_left 真正的节点
0x7fffffffe388: 0x606040          
(gdb) x/1xw 0x606040+32+8        # 偏移32字节是节点值的地址，再偏移8则是value的地址
0x606068:       0x00000bbb
(gdb) p *(char**)(0x606040+32)   # 偏移32字节是string的地址
$8 = 0x606028 "abc"
</code></pre>

<p>或者很多时候没有必要这么装逼+蛋疼：</p>

<pre><code>(gdb) p *(char**)(imap._M_t._M_impl._M_header._M_left+1)
$9 = 0x606028 "abc"
(gdb) x/1xw (char*)(imap._M_t._M_impl._M_header._M_left+1)+8
0x606068:       0x00000bbb
</code></pre>

<p><em>完</em></p>

<p class="post-footer">
            原文地址：
            <a href="http://codemacro.com/2014/12/03/gdb_stl/">http://codemacro.com/2014/12/03/gdb_stl/</a><br />
            written by <a href="http://codemacro.com">Kevin Lynx</a>
            &nbsp;posted at <a href="http://codemacro.com">http://codemacro.com</a>
            </p>

</div><img src ="http://www.cppblog.com/kevinlynx/aggbug/209016.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/kevinlynx/" target="_blank">Kevin Lynx</a> 2014-12-03 22:08 <a href="http://www.cppblog.com/kevinlynx/archive/2014/12/03/209016.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>linux动态库的种种要点</title><link>http://www.cppblog.com/kevinlynx/archive/2014/11/04/208761.html</link><dc:creator>Kevin Lynx</dc:creator><author>Kevin Lynx</author><pubDate>Mon, 03 Nov 2014 16:55:00 GMT</pubDate><guid>http://www.cppblog.com/kevinlynx/archive/2014/11/04/208761.html</guid><wfw:comment>http://www.cppblog.com/kevinlynx/comments/208761.html</wfw:comment><comments>http://www.cppblog.com/kevinlynx/archive/2014/11/04/208761.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/kevinlynx/comments/commentRss/208761.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/kevinlynx/services/trackbacks/208761.html</trackback:ping><description><![CDATA[<div class="entry-content">
<p>linux下使用动态库，基本用起来还是很容易。但如果我们的程序中大量使用动态库来实现各种框架/插件，那么就会遇到一些坑，掌握这些坑才有利于程序更稳健地运行。</p>

<p>本篇先谈谈动态库符号方面的问题。</p>

<p>测试代码可以在<a href="https://github.com/kevinlynx/test/tree/master/dytest">github上找到</a></p>

<h2>符号查找</h2>

<p>一个应用程序<code>test</code>会链接一个动态库<code>libdy.so</code>，如果一个符号，例如函数<code>callfn</code>定义于libdy.so中，test要使用该函数，简单地声明即可：</p>

<div class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="c1">// dy.cpp libdy.so</span>
<span class="kt">void</span> <span class="nf">callfn</span><span class="p">()</span> <span class="p">{</span>
    <span class="p">...</span>
<span class="p">}</span>

<span class="c1">// main.cpp test</span>
<span class="k">extern</span> <span class="kt">void</span> <span class="nf">callfn</span><span class="p">();</span>

<span class="n">callfn</span><span class="p">();</span></code></pre></div>


<p>在链接test的时候，链接器会统一进行检查。</p>

<p>同样，在libdy.so中有相同的规则，它可以使用一个外部的符号，<strong>在它被链接/载入进一个可执行程序时才会进行符号存在与否的检查</strong>。这个符号甚至可以定义在test中，形成一种双向依赖，或定义在其他动态库中：</p>

<!-- more -->




<div class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="c1">// dy.cpp libdy.so</span>
<span class="k">extern</span> <span class="kt">void</span> <span class="nf">mfunc</span><span class="p">();</span>

<span class="n">mfunc</span><span class="p">();</span>

<span class="c1">// main.cpp test</span>
<span class="kt">void</span> <span class="nf">mfunc</span><span class="p">()</span> <span class="p">{</span>
    <span class="p">...</span>
<span class="p">}</span></code></pre></div>


<p>在生成libdy.so时<code>mfunc</code>可以找不到，此时<code>mfunc</code>为未定义：</p>

<pre><code>$ nm libdy.so | grep mfun
U _Z5mfuncv
</code></pre>

<p>但在libdy.so被链接进test时则会进行检查，试着把<code>mfunc</code>函数的定义去掉，就会得到一个链接错误：</p>

<pre><code>./libdy.so: undefined reference to `mfunc()'
</code></pre>

<p>同样，如果我们动态载入libdy.so，此时当然可以链接通过，但是在载入时同样得到找不到符号的错误：</p>

<div class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="cp">#ifdef DY_LOAD</span>
    <span class="kt">void</span> <span class="o">*</span><span class="n">dp</span> <span class="o">=</span> <span class="n">dlopen</span><span class="p">(</span><span class="s">"./libdy.so"</span><span class="p">,</span> <span class="n">RTLD_LAZY</span><span class="p">);</span>
    <span class="k">typedef</span> <span class="nf">void</span> <span class="p">(</span><span class="o">*</span><span class="n">callfn</span><span class="p">)();</span>
    <span class="n">callfn</span> <span class="n">f</span> <span class="o">=</span> <span class="p">(</span><span class="n">callfn</span><span class="p">)</span> <span class="n">dlsym</span><span class="p">(</span><span class="n">dp</span><span class="p">,</span> <span class="s">"callfn"</span><span class="p">);</span>
    <span class="n">f</span><span class="p">();</span>
    <span class="n">dlclose</span><span class="p">(</span><span class="n">dp</span><span class="p">);</span>
<span class="cp">#else</span>
    <span class="n">callfn</span><span class="p">();</span>
<span class="cp">#endif</span></code></pre></div>


<p>得到错误：</p>

<pre><code>./test: symbol lookup error: ./libdy.so: undefined symbol: _Z5mfuncv
</code></pre>

<p><strong>结论：</strong>基于以上，我们知道，如果一个动态库依赖了一些外部符号，这些外部符号可以位于其他动态库甚至应用程序中。我们可以再链接这个动态库的时候就把依赖的其他库也链接上，或者推迟到链接应用程序时再链接。而动态加载的库，则要保证在加载该库时，进程中加载的其他动态库里已经存在该符号。</p>

<p>例如，通过<code>LD_PRELOAD</code>环境变量可以让一个进程先加载指定的动态库，上面那个动态加载启动失败的例子，可以通过预先加载包含<code>mfunc</code>符号的动态库解决：</p>

<pre><code>$ LD_PRELOAD=libmfun.so ./test
...
</code></pre>

<p>但是如果这个符号存在于可执行程序中则不行：</p>

<pre><code>$ nm test | grep mfunc
0000000000400a00 T _Z5mfuncv
$ nm test | grep mfunc
0000000000400a00 T _Z5mfuncv
$ ./test
...
./test: symbol lookup error: ./libdy.so: undefined symbol: _Z5mfuncv
</code></pre>

<h2>符号覆盖</h2>

<p>前面主要讲的是符号缺少的情况，如果同一个符号存在多分，则更能引发问题。这里谈到的符号都是全局符号，一个进程中某个全局符号始终是全局唯一的。为了保证这一点，在链接或动态载入动态库时，就会出现忽略重复符号的情况。</p>

<p><em>这里就不提同一个链接单位（如可执行程序、动态库）里符号重复的问题了</em></p>

<h3>函数</h3>

<p>当动态库和libdy.so可执行程序test中包含同名的函数时会怎样？根据是否动态加载情况还有所不同。</p>

<p>当直接链接动态库时，libdy.so和test都会链接包含<code>func</code>函数的fun.o，为了区分，我把<code>func</code>按照条件编译得到不同的版本：</p>

<div class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="c1">// fun.cpp</span>
<span class="cp">#ifdef V2</span>
<span class="k">extern</span> <span class="s">"C"</span> <span class="kt">void</span> <span class="n">func</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"func v2</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
<span class="p">}</span>
<span class="cp">#else</span>
<span class="k">extern</span> <span class="s">"C"</span> <span class="kt">void</span> <span class="n">func</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"func v1</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
<span class="p">}</span>
<span class="cp">#endif</span>

<span class="c1">// Makefile</span>
<span class="nl">test</span><span class="p">:</span> <span class="n">libdy</span> <span class="n">obj</span><span class="p">.</span><span class="n">o</span> <span class="n">mainfn</span>
    <span class="n">g</span><span class="o">++</span> <span class="o">-</span><span class="n">g</span> <span class="o">-</span><span class="n">Wall</span> <span class="o">-</span><span class="n">c</span> <span class="n">fun</span><span class="p">.</span><span class="n">cpp</span> <span class="o">-</span><span class="n">o</span> <span class="n">fun</span><span class="p">.</span><span class="n">o</span> <span class="err">#</span> <span class="err">编译为</span><span class="n">fun</span><span class="p">.</span><span class="n">o</span>
    <span class="n">g</span><span class="o">++</span> <span class="o">-</span><span class="n">g</span> <span class="o">-</span><span class="n">Wall</span> <span class="o">-</span><span class="n">c</span> <span class="n">main</span><span class="p">.</span><span class="n">cpp</span> <span class="err">#</span><span class="o">-</span><span class="n">DDY_LOAD</span>
    <span class="n">g</span><span class="o">++</span> <span class="o">-</span><span class="n">g</span> <span class="o">-</span><span class="n">Wall</span> <span class="o">-</span><span class="n">o</span> <span class="n">test</span> <span class="n">main</span><span class="p">.</span><span class="n">o</span> <span class="n">obj</span><span class="p">.</span><span class="n">o</span> <span class="n">fun</span><span class="p">.</span><span class="n">o</span> <span class="o">-</span><span class="n">ldl</span> <span class="n">mfun</span><span class="p">.</span><span class="n">o</span> <span class="o">-</span><span class="n">ldy</span> <span class="o">-</span><span class="n">L</span><span class="p">.</span>

<span class="nl">libdy</span><span class="p">:</span> <span class="n">obj</span>
    <span class="n">g</span><span class="o">++</span> <span class="o">-</span><span class="n">Wall</span> <span class="o">-</span><span class="n">fPIC</span> <span class="o">-</span><span class="n">c</span> <span class="n">fun</span><span class="p">.</span><span class="n">cpp</span> <span class="o">-</span><span class="n">DV2</span> <span class="o">-</span><span class="n">o</span> <span class="n">fun</span><span class="o">-</span><span class="n">dy</span><span class="p">.</span><span class="n">o</span>  <span class="err">#</span> <span class="err">定义</span><span class="n">V2</span><span class="err">宏，编译为</span><span class="n">fun</span><span class="o">-</span><span class="n">dy</span><span class="p">.</span><span class="n">o</span>
    <span class="n">g</span><span class="o">++</span> <span class="o">-</span><span class="n">Wall</span> <span class="o">-</span><span class="n">fPIC</span> <span class="o">-</span><span class="n">shared</span> <span class="o">-</span><span class="n">o</span> <span class="n">libdy</span><span class="p">.</span><span class="n">so</span> <span class="n">dy</span><span class="p">.</span><span class="n">cpp</span> <span class="o">-</span><span class="n">g</span> <span class="n">obj</span><span class="p">.</span><span class="n">o</span> <span class="n">fun</span><span class="o">-</span><span class="n">dy</span><span class="p">.</span><span class="n">o</span></code></pre></div>


<p>这样，test中的<code>func</code>就会输出<code>func v1</code>；libdy.so中的<code>func</code>就会输出<code>func v2</code>。test和libdy.o确实都有<code>func</code>符号：</p>

<pre><code>$ nm libdy.so | grep func
0000000000000a60 T func

$nm test | grep func
0000000000400a80 T func
</code></pre>

<p>在test和libdy.so中都会调用<code>func</code>函数：</p>

<div class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="c1">// main.cpp test</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">func</span><span class="p">();</span>
    <span class="p">...</span>
    <span class="n">callfn</span><span class="p">();</span> <span class="c1">// 调用libdy.so中的函数</span>
    <span class="p">...</span>
<span class="p">}</span>

<span class="c1">// dy.cpp libdy.so</span>
<span class="k">extern</span> <span class="s">"C"</span> <span class="kt">void</span> <span class="n">callfn</span><span class="p">()</span> <span class="p">{</span>
    <span class="p">...</span> 
    <span class="n">printf</span><span class="p">(</span><span class="s">"callfn</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
    <span class="n">func</span><span class="p">();</span>
    <span class="p">...</span>
<span class="p">}</span></code></pre></div>


<p>运行后发现，都<strong>调用的是同一个<code>func</code></strong>：</p>

<pre><code>$ ./test
...
func v1
...
callfn
func v1
</code></pre>

<p><strong>结论</strong>，直接链接动态库时，整个程序运行的时候符号会发生覆盖，只有一个符号被使用。<strong>在实践中</strong>，如果程序和链接的动态库都依赖了一个静态库，而后他们链接的这个静态库版本不同，则很有可能因为符号发生了覆盖而导致问题。(静态库同普通的.o性质一样，参考<a href="http://codemacro.com/2014/09/15/inside-static-library/">浅析静态库链接原理</a>)</p>

<p>更复杂的情况中，多个动态库和程序都有相同的符号，情况也是一样，会发生符号覆盖。如果程序里没有这个符号，而多个动态库里有相同的符号，也会覆盖。</p>

<p>但是对于动态载入的情况则不同，同样的libdy.so我们在test中不链接，而是动态载入：</p>

<div class="highlight"><pre><code class="language-c--" data-lang="c++"><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">func</span><span class="p">();</span>
<span class="cp">#ifdef DY_LOAD</span>
    <span class="kt">void</span> <span class="o">*</span><span class="n">dp</span> <span class="o">=</span> <span class="n">dlopen</span><span class="p">(</span><span class="s">"./libdy.so"</span><span class="p">,</span> <span class="n">RTLD_LAZY</span><span class="p">);</span>
    <span class="k">typedef</span> <span class="kt">void</span> <span class="p">(</span><span class="o">*</span><span class="n">callfn</span><span class="p">)();</span>
    <span class="n">callfn</span> <span class="n">f</span> <span class="o">=</span> <span class="p">(</span><span class="n">callfn</span><span class="p">)</span> <span class="n">dlsym</span><span class="p">(</span><span class="n">dp</span><span class="p">,</span> <span class="s">"callfn"</span><span class="p">);</span>
    <span class="n">f</span><span class="p">();</span>
    <span class="n">func</span><span class="p">();</span>
    <span class="n">dlclose</span><span class="p">(</span><span class="n">dp</span><span class="p">);</span>
<span class="cp">#else</span>
    <span class="n">callfn</span><span class="p">();</span>
<span class="cp">#endif</span>
    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span></code></pre></div>


<p>运行得到：</p>

<pre><code>$ ./test
func v1
...
callfn
func v2
func v1
</code></pre>

<p>都正确地调用到各自链接的<code>func</code>。</p>

<p><strong>结论</strong>，实践中，动态载入的动态库一般会作为插件使用，那么其同程序链接不同版本的静态库（相同符号不同实现），是没有问题的。</p>

<h3>变量</h3>

<p>变量本质上也是符号(symbol)，但其处理规则和函数还有点不一样(<em>是不是有点想吐槽了</em>)。</p>

<div class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="c1">// object.h</span>
<span class="k">class</span> <span class="nc">Object</span> <span class="p">{</span>
<span class="k">public</span><span class="o">:</span>
    <span class="n">Object</span><span class="p">()</span> <span class="p">{</span>
<span class="cp">#ifdef DF</span>
        <span class="n">s</span> <span class="o">=</span> <span class="n">malloc</span><span class="p">(</span><span class="mi">32</span><span class="p">);</span>
        <span class="n">printf</span><span class="p">(</span><span class="s">"s addr %p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">s</span><span class="p">);</span>
<span class="cp">#endif</span>
        <span class="n">printf</span><span class="p">(</span><span class="s">"ctor %p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="k">this</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="o">~</span><span class="n">Object</span><span class="p">()</span> <span class="p">{</span>
        <span class="n">printf</span><span class="p">(</span><span class="s">"dtor %p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="k">this</span><span class="p">);</span>
<span class="cp">#ifdef DF</span>
        <span class="n">printf</span><span class="p">(</span><span class="s">"s addr %p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">s</span><span class="p">);</span>
        <span class="n">free</span><span class="p">(</span><span class="n">s</span><span class="p">);</span>
<span class="cp">#endif</span>
    <span class="p">}</span>

    <span class="kt">void</span> <span class="o">*</span><span class="n">s</span><span class="p">;</span>
<span class="p">};</span>

<span class="k">extern</span> <span class="n">Object</span> <span class="n">g_obj</span><span class="p">;</span></code></pre></div>


<p>我们的程序test和动态库libdy.so都会链接object.o。首先测试test链接libdy.so，test和libdy.so中都会有<code>g_obj</code>这个符号：</p>

<pre><code>// B g_obj 表示g_obj位于BSS段，未初始化段

$ nm test | grep g_obj
0000000000400a14 t _GLOBAL__I_g_obj
00000000006012c8 B g_obj
$ nm libdy.so | grep g_obj
000000000000097c t _GLOBAL__I_g_obj
0000000000200f30 B g_obj
</code></pre>

<p>运行：</p>

<pre><code>$ ./test
ctor 0x6012c8
ctor 0x6012c8
...
dtor 0x6012c8
dtor 0x6012c8
</code></pre>

<p><strong><code>g_obj</code>被构造了两次，但地址一样</strong>。全局变量只有一个实例，似乎在情理之中。</p>

<p>动态载入libdy.so，变量地址还是相同的：</p>

<pre><code>$ ./test
ctor 0x6012a8
...
ctor 0x6012a8
...
dtor 0x6012a8
dtor 0x6012a8
</code></pre>

<p><strong>结论</strong>，不同于函数，全局变量符号重复时，不论动态库是动态载入还是直接链接，变量始终只有一个。</p>

<p>但诡异的情况是，对象被构造和析构了两次。构造两次倒无所谓，浪费点空间，但是析构两次就有问题。因为析构时都操作的是同一个对象，那么如果这个对象内部有分配的内存，那就会对这块内存造成double free，因为指针相同。打开<code>DF</code>宏实验下：</p>

<pre><code>$ ./test
s addr 0x20de010
ctor 0x6012b8
s addr 0x20de040
ctor 0x6012b8
...
dtor 0x6012b8
s addr 0x20de040
dtor 0x6012b8
s addr 0x20de040
</code></pre>

<p>因为析构的两次都是同一个对象，所以其成员<code>s</code>指向的内存被释放了两次，从而产生了double free，让程序coredump了。</p>

<p><strong>总结</strong>，全局变量符号重复时，始终会只使用一个，并且会被初始化/释放两次，是一种较危险的情况，应当避免在使用动态库的过程中使用全局变量。</p>

<p><em>完</em></p>

<p class="post-footer">
            原文地址：
            <a href="http://codemacro.com/2014/11/04/linux-dynamic-library/">http://codemacro.com/2014/11/04/linux-dynamic-library/</a><br />
            written by <a href="http://codemacro.com">Kevin Lynx</a>
            &nbsp;posted at <a href="http://codemacro.com">http://codemacro.com</a>
            </p>

</div><img src ="http://www.cppblog.com/kevinlynx/aggbug/208761.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/kevinlynx/" target="_blank">Kevin Lynx</a> 2014-11-04 00:55 <a href="http://www.cppblog.com/kevinlynx/archive/2014/11/04/208761.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>浅析glibc中thread tls的一处bug</title><link>http://www.cppblog.com/kevinlynx/archive/2014/10/07/208509.html</link><dc:creator>Kevin Lynx</dc:creator><author>Kevin Lynx</author><pubDate>Tue, 07 Oct 2014 13:38:00 GMT</pubDate><guid>http://www.cppblog.com/kevinlynx/archive/2014/10/07/208509.html</guid><wfw:comment>http://www.cppblog.com/kevinlynx/comments/208509.html</wfw:comment><comments>http://www.cppblog.com/kevinlynx/archive/2014/10/07/208509.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cppblog.com/kevinlynx/comments/commentRss/208509.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/kevinlynx/services/trackbacks/208509.html</trackback:ping><description><![CDATA[<div class="entry-content">
<p>最早的时候是在程序初始化过程中开启了一个timer(<code>timer_create</code>)，这个timer第一次触发的时间较短时就会引起程序core掉，core的位置也是不定的。使用valgrind可以发现有错误的内存写入：</p>

<pre><code>==31676== Invalid write of size 8
==31676==    at 0x37A540F852: _dl_allocate_tls_init (in /lib64/ld-2.5.so)
==31676==    by 0x4E26BD3: pthread_create@@GLIBC_2.2.5 (in /lib64/libpthread-2.5.so)
==31676==    by 0x76E0B00: timer_helper_thread (in /lib64/librt-2.5.so)
==31676==    by 0x4E2673C: start_thread (in /lib64/libpthread-2.5.so)
==31676==    by 0x58974BC: clone (in /lib64/libc-2.5.so)
==31676==  Address 0xf84dbd0 is 0 bytes after a block of size 336 alloc'd
==31676==    at 0x4A05430: calloc (vg_replace_malloc.c:418)
==31676==    by 0x37A5410082: _dl_allocate_tls (in /lib64/ld-2.5.so)
==31676==    by 0x4E26EB8: pthread_create@@GLIBC_2.2.5 (in /lib64/libpthread-2.5.so)
==31676==    by 0x76E0B00: timer_helper_thread (in /lib64/librt-2.5.so)
==31676==    by 0x4E2673C: start_thread (in /lib64/libpthread-2.5.so)
==31676==    by 0x58974BC: clone (in /lib64/libc-2.5.so)
</code></pre>

<p>google <code>_dl_allocate_tls_init</code> 相关发现一个glibc的bug <a href="https://sourceware.org/bugzilla/show_bug.cgi?id=13862">Bug 13862</a> 和我的情况有点类似。本文就此bug及tls相关实现做一定阐述。</p>

<p>需要查看glibc的源码，如何确认使用的glibc的版本，可以这样：</p>

<pre><code>$ /lib/libc.so.6
GNU C Library stable release version 2.5, by Roland McGrath et al.
...
</code></pre>

<p>为了方便，还可以直接在(glibc Cross Reference)[<a href="http://osxr.org/glibc/source/?v=glibc-2.17">http://osxr.org/glibc/source/?v=glibc-2.17</a>]网页上进行查看，版本不同，但影响不大。</p>

<!-- more -->


<h2>BUG描述</h2>

<p>要重现13862 BUG作者提到要满足以下条件：</p>

<blockquote>
<p>The use of a relatively large number of dynamic libraries, loaded at runtime using dlopen.</p>

<p>The use of thread-local-storage within those libraries.</p>

<p>A thread exiting prior to the number of loaded libraries increasing a significant amount, followed by a new thread being created after the number of libraries has increased.</p>
</blockquote>

<p>简单来说，就是在加载一大堆包含TLS变量的动态库的过程中，开启了一个线程，这个线程退出后又开启了另一个线程。</p>

<p>这和我们的问题场景很相似。不同的是我们使用的是timer，但timer在触发时也是开启新的线程，并且这个线程会立刻退出：</p>

<p><code>/nptl/sysdeps/unix/sysv/linux/timer_routines.c</code></p>

<div class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="n">timer_helper_thread</span><span class="p">(...)</span>  <span class="c1">// 用于检测定时器触发的辅助线程</span>
<span class="p">{</span>
    <span class="p">...</span>
      <span class="kt">pthread_t</span> <span class="n">th</span><span class="p">;</span>
      <span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="n">pthread_create</span> <span class="p">(</span><span class="o">&amp;</span><span class="n">th</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">tk</span><span class="o">-&gt;</span><span class="n">attr</span><span class="p">,</span> <span class="n">timer_sigev_thread</span><span class="p">,</span> <span class="c1">// 开启一个新线程调用用户注册的定时器函数</span>
                 <span class="n">td</span><span class="p">);</span>
    <span class="p">...</span>
<span class="p">}</span></code></pre></div>


<p>要重现此BUG可以使用我的实验代码 <a href="https://gist.github.com/kevinlynx/69435e718785a0ad12c4">thread-tls</a>，或者使用<a href="https://sourceware.org/bugzilla/attachment.cgi?id=6290">Bug 13862 中的附件</a></p>

<h2>TLS相关实现</h2>

<p>可以顺着<code>_dl_allocate_tls_init</code>函数的实现查看相关联的部分代码。该函数遍历所有加载的包含TLS变量的模块，初始化一个线程的TLS数据结构。</p>

<p>每一个线程都有自己的堆栈空间，其中单独存储了各个模块的TLS变量，从而实现TLS变量在每一个线程中都有单独的拷贝。TLS与线程的关联关系可以查看下图：</p>

<p><img src="http://codemacro.com/assets/res/pthread-tls.png" alt="" /></p>

<p>应用层使用的<code>pthread_t</code>实际是个<code>pthread</code>对象的地址。创建线程时线程的堆栈空间和<code>pthread</code>结构是一块连续的内存。但这个地址并不指向这块内存的首地址。相关代码：/nptl/allocatestack.c <code>allocate_stack</code>，该函数分配线程的堆栈内存。</p>

<p><code>pthread</code>第一个成员是<code>tcbhead_t</code>，<code>tcbhead_t</code>中<code>dtv</code>指向了一个<code>dtv_t</code>数组，该数组的大小随着当前程序载入的模块多少而动态变化。每一个模块被载入时，都有一个<code>l_tls_modid</code>，其直接作为<code>dtv_t</code>数组的下标索引。<code>tcbhead_t</code>中的<code>dtv</code>实际指向的是<code>dtv_t</code>第二个元素，第一个元素用于记录整个<code>dtv_t</code>数组有多少元素，第二个元素也做特殊使用，从第三个元素开始，才是用于存储TLS变量。</p>

<p>一个<code>dtv_t</code>存储的是一个模块中所有TLS变量的地址，当然这些TLS变量都会被放在连续的内存空间里。<code>dtv_t::pointer::val</code>正是用于指向这块内存的指针。对于非动态加载的模块它指向的是线程堆栈的位置；否则指向动态分配的内存位置。</p>

<p>以上结构用代码描述为，</p>

<div class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="k">union</span> <span class="kt">dtv_t</span> <span class="p">{</span>
    <span class="kt">size_t</span> <span class="n">counter</span><span class="p">;</span>
    <span class="k">struct</span> <span class="p">{</span>
        <span class="kt">void</span> <span class="o">*</span><span class="n">val</span><span class="p">;</span> <span class="cm">/* point to tls variable memory */</span>
        <span class="kt">bool</span> <span class="n">is_static</span><span class="p">;</span>
    <span class="p">}</span> <span class="n">pointer</span><span class="p">;</span>
<span class="p">};</span>
 
<span class="k">struct</span> <span class="kt">tcbhead_t</span> <span class="p">{</span>
    <span class="kt">void</span> <span class="o">*</span><span class="n">tcb</span><span class="p">;</span>
    <span class="kt">dtv_t</span> <span class="o">*</span><span class="n">dtv</span><span class="p">;</span> <span class="cm">/* point to a dtv_t array */</span>
    <span class="kt">void</span> <span class="o">*</span><span class="n">padding</span><span class="p">[</span><span class="mi">22</span><span class="p">];</span> <span class="cm">/* other members i don't care */</span>
<span class="p">};</span>

<span class="k">struct</span> <span class="n">pthread</span> <span class="p">{</span>
    <span class="kt">tcbhead_t</span> <span class="n">tcb</span><span class="p">;</span>
    <span class="cm">/* more members i don't care */</span>
<span class="p">};</span></code></pre></div>


<p><strong>dtv是一个用于以模块为单位存储TLS变量的数组</strong>。</p>

<p>实际代码参看 /nptl/descr.h 及 nptl/sysdeps/x86_64/tls.h。</p>

<h3>实验</h3>

<p>使用<code>g++ -o thread -g -Wall -lpthread -ldl thread.cpp</code>编译<a href="https://gist.github.com/kevinlynx/69435e718785a0ad12c4">代码</a>，即在创建线程前加载了一个.so：</p>

<pre><code>Breakpoint 1, dump_pthread (id=1084229952) at thread.cpp:40
40          printf("pthread %p, dtv %p\n", pd, dtv);
(gdb) set $dtv=pd-&gt;tcb.dtv
(gdb) p $dtv[-1]
$1 = {counter = 17, pointer = {val = 0x11, is_static = false}}
(gdb) p $dtv[3]
$2 = {counter = 18446744073709551615, pointer = {val = 0xffffffffffffffff, is_static = false}}
</code></pre>

<p><code>dtv[3]</code>对应着动态加载的模块，<code>is_static=false</code>，<code>val</code>被初始化为-1：</p>

<p>/elf/dl-tls.c <code>_dl_allocate_tls_init</code></p>

<div class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="k">if</span> <span class="p">(</span><span class="n">map</span><span class="o">-&gt;</span><span class="n">l_tls_offset</span> <span class="o">==</span> <span class="n">NO_TLS_OFFSET</span>
   <span class="o">||</span> <span class="n">map</span><span class="o">-&gt;</span><span class="n">l_tls_offset</span> <span class="o">==</span> <span class="n">FORCED_DYNAMIC_TLS_OFFSET</span><span class="p">)</span>
 <span class="p">{</span>
   <span class="cm">/* For dynamically loaded modules we simply store</span>
<span class="cm">      the value indicating deferred allocation.  */</span>
   <span class="n">dtv</span><span class="p">[</span><span class="n">map</span><span class="o">-&gt;</span><span class="n">l_tls_modid</span><span class="p">].</span><span class="n">pointer</span><span class="p">.</span><span class="n">val</span> <span class="o">=</span> <span class="n">TLS_DTV_UNALLOCATED</span><span class="p">;</span>
   <span class="n">dtv</span><span class="p">[</span><span class="n">map</span><span class="o">-&gt;</span><span class="n">l_tls_modid</span><span class="p">].</span><span class="n">pointer</span><span class="p">.</span><span class="n">is_static</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>
   <span class="k">continue</span><span class="p">;</span>
 <span class="p">}</span></code></pre></div>


<p><code>dtv</code>数组大小之所以为17，可以参看代码 /elf/dl-tls.c <code>allocate_dtv</code>：</p>

<div class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="c1">// dl_tls_max_dtv_idx 随着载入模块的增加而增加，载入1个.so则是1 </span>

<span class="n">dtv_length</span> <span class="o">=</span> <span class="n">GL</span><span class="p">(</span><span class="n">dl_tls_max_dtv_idx</span><span class="p">)</span> <span class="o">+</span> <span class="n">DTV_SURPLUS</span><span class="p">;</span> <span class="c1">// DTV_SURPLUS 14</span>
<span class="n">dtv</span> <span class="o">=</span> <span class="n">calloc</span> <span class="p">(</span><span class="n">dtv_length</span> <span class="o">+</span> <span class="mi">2</span><span class="p">,</span> <span class="k">sizeof</span> <span class="p">(</span><span class="kt">dtv_t</span><span class="p">));</span>
<span class="k">if</span> <span class="p">(</span><span class="n">dtv</span> <span class="o">!=</span> <span class="nb">NULL</span><span class="p">)</span>
 <span class="p">{</span>
   <span class="cm">/* This is the initial length of the dtv.  */</span>
   <span class="n">dtv</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">counter</span> <span class="o">=</span> <span class="n">dtv_length</span><span class="p">;</span></code></pre></div>


<p>继续上面的实验，当调用到.so中的<code>function</code>时，其TLS被初始化，此时<code>dtv[3]</code>中<code>val</code>指向初始化后的TLS变量地址：</p>

<pre><code>68          fn();
(gdb)
0x601808, 0x601804, 0x601800
72          return 0;
(gdb) p $dtv[3]
$3 = {counter = 6297600, pointer = {val = 0x601800, is_static = false}}
(gdb) x/3xw 0x601800
0x601800:       0x55667788      0xaabbccdd      0x11223344
</code></pre>

<p>这个时候还可以看看<code>dtv[1]</code>中的内容，正是指向了<code>pthread</code>前面的内存位置：</p>

<pre><code>(gdb) p $dtv[1]
$5 = {counter = 1084229936, pointer = {val = 0x40a00930, is_static = true}}
(gdb) p/x tid
$7 = 0x40a00940
</code></pre>

<p><strong>结论</strong>:</p>

<ul>
<li>线程中TLS变量的存储是以模块为单位的</li>
</ul>


<h2>so模块加载</h2>

<p>这里也并不太需要查看<code>dlopen</code>等具体实现，由于使用<code>__thread</code>来定义TLS变量，整个实现涉及到ELF加载器的一些细节，深入下去内容较多。这里直接通过实验的手段来了解一些实现即可。</p>

<p>上文已经看到，<strong>在创建线程前如果动态加载了.so，dtv数组的大小是会随之增加的</strong>。如果是在线程创建后再载入.so呢？</p>

<p>使用<code>g++ -o thread -g -Wall -lpthread -ldl thread.cpp -DTEST_DTV_EXPAND -DSO_CNT=1</code>编译程序，调试得到：</p>

<pre><code>73          load_sos();
(gdb)
0x601e78, 0x601e74, 0x601e70

Breakpoint 1, dump_pthread (id=1084229952) at thread.cpp:44
44          printf("pthread %p, dtv %p\n", pd, dtv);
(gdb) p $dtv[-1]
$3 = {counter = 17, pointer = {val = 0x11, is_static = false}}
(gdb) p $dtv[4]
$4 = {counter = 6299248, pointer = {val = 0x601e70, is_static = false}}
</code></pre>

<p>在新载入了.so时，<code>dtv</code>数组大小并没有新增，<code>dtv[4]</code>直接被拿来使用。</p>

<p>因为<code>dtv</code>初始大小为16，那么当载入的.so超过这个数字的时候会怎样？</p>

<p>使用<code>g++ -o thread -g -Wall -lpthread -ldl thread.cpp -DTEST_DTV_EXPAND</code>编译程序：</p>

<pre><code>...
pthread 0x40a00940, dtv 0x6016a0
...
Breakpoint 1, dump_pthread (id=1084229952) at thread.cpp:44
44          printf("pthread %p, dtv %p\n", pd, dtv);
(gdb) p dtv
$2 = (dtv_t *) 0x6078a0
(gdb) p dtv[-1]
$3 = {counter = 32, pointer = {val = 0x20, is_static = false}}
(gdb) p dtv[5]
$4 = {counter = 6300896, pointer = {val = 0x6024e0, is_static = false}}
</code></pre>

<p>可以看出，<code>dtv</code>被重新分配了内存(0x6016a0 -&gt; 0x6078a0)并做了扩大。</p>

<p>以上得出结论：</p>

<ul>
<li>创建线程前dtv的大小会根据载入模块数量决定</li>
<li>创建线程后新载入的模块会动态扩展dtv的大小(必要的时候)</li>
</ul>


<h2>pthread堆栈重用</h2>

<p>在<code>allocate_stack</code>中分配线程堆栈时，有一个从缓存中取的操作：</p>

<div class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="n">allocate_stack</span><span class="p">(..)</span> <span class="p">{</span>
    <span class="p">...</span>
    <span class="n">pd</span> <span class="o">=</span> <span class="n">get_cached_stack</span> <span class="p">(</span><span class="o">&amp;</span><span class="n">size</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">mem</span><span class="p">);</span>
    <span class="p">...</span>
<span class="p">}</span>
<span class="cm">/* Get a stack frame from the cache.  We have to match by size since</span>
<span class="cm">   some blocks might be too small or far too large.  */</span>
<span class="n">get_cached_stack</span><span class="p">(...)</span> <span class="p">{</span>
    <span class="p">...</span>
    <span class="n">list_for_each</span> <span class="p">(</span><span class="n">entry</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">stack_cache</span><span class="p">)</span> <span class="c1">// 根据size从stack_cache中取</span>
    <span class="p">{</span> <span class="p">...</span> <span class="p">}</span>
    <span class="p">...</span>
    <span class="cm">/* Clear the DTV.  */</span>
    <span class="kt">dtv_t</span> <span class="o">*</span><span class="n">dtv</span> <span class="o">=</span> <span class="n">GET_DTV</span> <span class="p">(</span><span class="n">TLS_TPADJ</span> <span class="p">(</span><span class="n">result</span><span class="p">));</span>
    <span class="k">for</span> <span class="p">(</span><span class="kt">size_t</span> <span class="n">cnt</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">cnt</span> <span class="o">&lt;</span> <span class="n">dtv</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">].</span><span class="n">counter</span><span class="p">;</span> <span class="o">++</span><span class="n">cnt</span><span class="p">)</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span> <span class="n">dtv</span><span class="p">[</span><span class="mi">1</span> <span class="o">+</span> <span class="n">cnt</span><span class="p">].</span><span class="n">pointer</span><span class="p">.</span><span class="n">is_static</span>
                <span class="o">&amp;&amp;</span> <span class="n">dtv</span><span class="p">[</span><span class="mi">1</span> <span class="o">+</span> <span class="n">cnt</span><span class="p">].</span><span class="n">pointer</span><span class="p">.</span><span class="n">val</span> <span class="o">!=</span> <span class="n">TLS_DTV_UNALLOCATED</span><span class="p">)</span>
            <span class="n">free</span> <span class="p">(</span><span class="n">dtv</span><span class="p">[</span><span class="mi">1</span> <span class="o">+</span> <span class="n">cnt</span><span class="p">].</span><span class="n">pointer</span><span class="p">.</span><span class="n">val</span><span class="p">);</span>
    <span class="n">memset</span> <span class="p">(</span><span class="n">dtv</span><span class="p">,</span> <span class="sc">'\0'</span><span class="p">,</span> <span class="p">(</span><span class="n">dtv</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">].</span><span class="n">counter</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="o">*</span> <span class="k">sizeof</span> <span class="p">(</span><span class="kt">dtv_t</span><span class="p">));</span>

    <span class="cm">/* Re-initialize the TLS.  */</span>
    <span class="n">_dl_allocate_tls_init</span> <span class="p">(</span><span class="n">TLS_TPADJ</span> <span class="p">(</span><span class="n">result</span><span class="p">));</span>
<span class="p">}</span></code></pre></div>


<p><code>get_cached_stack</code>会把取出的<code>pthread</code>中的dtv重新初始化。<strong>注意 <code>_dl_allocate_tls_init</code> 中是根据模块列表来初始化dtv数组的。</strong></p>

<h3>实验</h3>

<p>当一个线程退出后，它就可能被当做cache被<code>get_cached_stack</code>取出复用。</p>

<p>使用<code>g++ -o thread -g -Wall -lpthread -ldl thread.cpp -DTEST_CACHE_STACK</code>编译程序，运行：</p>

<pre><code>$ ./thread
..
pthread 0x413c9940, dtv 0x1be46a0
... 
pthread 0x413c9940, dtv 0x1be46a0
</code></pre>

<h2>回顾BUG</h2>

<p>当新创建的线程复用了之前退出的线程堆栈时，由于在<code>_dl_allocate_tls_init</code>中初始化dtv数组时是根据当前载入的模块数量而定。如果在这个时候模块数已经超过了这个复用的dtv数组大小，那么就会出现写入非法的内存。使用valgrind检测就会得到本文开头提到的结果。</p>

<p>由于dtv数组大小通常会稍微大点，所以在新加载的模块数量不够多时程序还不会有问题。可以通过控制测试程序中<code>SO_CNT</code>的大小看看dtv中内容的变化。</p>

<p>另外，我查看了下glibc的更新历史，到目前为止(2.20)这个BUG还没有修复。</p>

<h2>参考文档</h2>

<ul>
<li><a href="https://sourceware.org/bugzilla/show_bug.cgi?id=13862">glibc Bug 13862 - Reuse of cached stack can cause bounds overrun of thread DTV</a></li>
<li><a href="http://tsecer.blog.163.com/blog/static/1501817201172883556743/">gLibc TLS实现</a></li>
<li><a href="http://blog.chinaunix.net/uid-24774106-id-3651266.html">Linux线程之线程栈</a></li>
<li><a href="http://www.longene.org/forum/viewtopic.php?f=17&amp;t=429">Linux用户空间线程管理介绍之二：创建线程堆栈</a></li>
</ul>


<p class="post-footer">
            原文地址：
            <a href="http://codemacro.com/2014/10/07/pthread-tls-bug/">http://codemacro.com/2014/10/07/pthread-tls-bug/</a><br />
            written by <a href="http://codemacro.com">Kevin Lynx</a>
            &nbsp;posted at <a href="http://codemacro.com">http://codemacro.com</a>
            </p>

</div><img src ="http://www.cppblog.com/kevinlynx/aggbug/208509.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/kevinlynx/" target="_blank">Kevin Lynx</a> 2014-10-07 21:38 <a href="http://www.cppblog.com/kevinlynx/archive/2014/10/07/208509.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>浅析静态库链接原理</title><link>http://www.cppblog.com/kevinlynx/archive/2014/09/15/208320.html</link><dc:creator>Kevin Lynx</dc:creator><author>Kevin Lynx</author><pubDate>Mon, 15 Sep 2014 14:47:00 GMT</pubDate><guid>http://www.cppblog.com/kevinlynx/archive/2014/09/15/208320.html</guid><wfw:comment>http://www.cppblog.com/kevinlynx/comments/208320.html</wfw:comment><comments>http://www.cppblog.com/kevinlynx/archive/2014/09/15/208320.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://www.cppblog.com/kevinlynx/comments/commentRss/208320.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/kevinlynx/services/trackbacks/208320.html</trackback:ping><description><![CDATA[<div class="entry-content">
<p>静态库的链接基本上同链接目标文件<code>.obj/.o</code>相同，但也有些不同的地方。本文简要描述linux下静态库在链接过程中的一些细节。</p>

<h2>静态库文件格式</h2>

<p>静态库远远不同于动态库，不涉及到符号重定位之类的问题。静态库本质上只是将一堆目标文件进行打包而已。静态库没有标准，不同的linux下都会有些细微的差别。大致的格式<a href="http://en.wikipedia.org/wiki/Ar_%28Unix%29#File_format_details">wiki</a>上描述的较清楚：</p>

<pre><code>Global header
-----------------        +-------------------------------
File header 1       ---&gt; | File name
File content 1  |        | File modification timestamp 
-----------------        | Owner ID
File header 2            | Group ID
File content 2           | File mode
-----------------        | File size in bytes
...                      | File magic
                         +-------------------------------
</code></pre>

<p><code>File header</code>很多字段都是以ASCII码表示，所以可以用文本编辑器打开。</p>

<p>静态库本质上就是使用<code>ar</code>命令打包一堆<code>.o</code>文件。我们甚至可以用<code>ar</code>随意打包一些文件：</p>

<pre><code>$ echo 'hello' &gt; a.txt &amp;&amp; echo 'world' &gt; b.txt
$ ar -r test.a a.txt b.txt
$ cat test.a
!&lt;arch&gt;
a.txt/          1410628755  60833 100   100644  6         `
hello
b.txt/          1410628755  60833 100   100644  6         `
world
</code></pre>

<!-- more -->


<h2>链接过程</h2>

<p>链接器在链接静态库时，同链接一般的<code>.o</code>基本相似。链接过程大致可以归纳下图：</p>

<p><img src="http://codemacro.com/assets/res/link-process.png" alt="" /></p>

<p>总结为：</p>

<ul>
<li>
<strong>所有传入链接器的<code>.o</code>都会被链接进最终的可执行程序</strong>；链接<code>.o</code>时，会将<code>.o</code>中的<code>global symbol</code>和<code>unresolved symbol</code>放入一个临时表</li>
<li>如果多个<code>.o</code>定义了相同的<code>global symbol</code>，那么就会得到多重定义的链接错误</li>
<li>如果链接结束了，<code>unresolved symbol</code>表不为空，那么就会得到符号未定义的链接错误</li>
<li>
<code>.a</code>静态库处理本质上就是处理其中的每一个<code>.o</code>，不同的是，如果某个<code>.o</code>中没有一个符号属于<code>unresolved symbol</code>表，也就是链接器此时怀疑该<code>.o</code>没有必要，那么其就会被忽略</li>
</ul>


<p>可以通过一些代码来展示以上过程。在开发C++程序时，可以利用文件静态变量会先于<code>main</code>之前执行做一些可能利于程序结构的事情。如果某个<code>.o</code>（包含静态库中打包的<code>.o</code>）被链接进程序，那么其文件静态变量就会先于<code>main</code>初始化。</p>

<div class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="c1">// test.cpp</span>
<span class="cp">#include &lt;stdio.h&gt;</span>

<span class="k">class</span> <span class="nc">Test</span> <span class="p">{</span>
<span class="k">public</span><span class="o">:</span>
    <span class="n">Test</span><span class="p">()</span> <span class="p">{</span>
        <span class="n">printf</span><span class="p">(</span><span class="s">"Test ctor</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">};</span>

<span class="k">static</span> <span class="n">Test</span> <span class="n">s_test</span><span class="p">;</span>

<span class="c1">// lib.cpp</span>
<span class="cp">#include &lt;stdio.h&gt;</span>

<span class="k">class</span> <span class="nc">Lib</span> <span class="p">{</span>
<span class="k">public</span><span class="o">:</span>
    <span class="n">Lib</span><span class="p">()</span> <span class="p">{</span>
        <span class="n">printf</span><span class="p">(</span><span class="s">"Lib ctor</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">};</span>

<span class="k">static</span> <span class="n">Lib</span> <span class="n">s_lib</span><span class="p">;</span>

<span class="c1">// main.cpp</span>
<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">"main</span><span class="se">\n</span><span class="s">"</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>以上代码<code>main.cpp</code>中未引用任何<code>test.cpp``lib.cpp</code>中的符号：</p>

<pre><code>$ g++ -o test test.o lib.o main.o
$ ./test
Lib ctor
Test ctor
main
</code></pre>

<p>生成的可执行程序执行如预期，其链接了<code>test.o``lib.o</code>。但是如果把<code>lib.o</code>以静态库的形式进行链接，情况就不一样了：为了做对比，基于以上的代码再加一个文件，及修改<code>main.cpp</code>：</p>

<div class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="c1">// libfn.cpp</span>
<span class="kt">int</span> <span class="nf">sum</span><span class="p">(</span><span class="kt">int</span> <span class="n">a</span><span class="p">,</span> <span class="kt">int</span> <span class="n">b</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="n">a</span> <span class="o">+</span> <span class="n">b</span><span class="p">;</span>
<span class="p">}</span>

<span class="c1">// main.cpp</span>
<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">"main</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
    <span class="k">extern</span> <span class="kt">int</span> <span class="n">sum</span><span class="p">(</span><span class="kt">int</span><span class="p">,</span> <span class="kt">int</span><span class="p">);</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"sum: %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">sum</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="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span></code></pre></div>


<p>将<code>libfn.o</code>和<code>lib.o</code>创建为静态库：</p>

<pre><code>$ ar -r libfn.a libfn.o lib.o
$ g++ -o test main.o test.o -lfn -L.
$ ./test
Test ctor
main
sum: 5
</code></pre>

<p>因为<code>lib.o</code>没有被链接，导致其文件静态变量也未得到初始化。</p>

<p>调整链接顺序，可以进一步检验前面的链接过程：</p>

<pre><code># 将libfn.a的链接放在main.o前面

$ g++ -o test test.o -lfn main.o  -L.
main.o: In function `main':
main.cpp:(.text+0x19): undefined reference to `sum(int, int)'
collect2: ld returned 1 exit status
</code></pre>

<p>这个问题遇到得比较多，也有点让人觉得莫名其妙。其原因就在于链接器在链接<code>libfn.a</code>的时候，发现<code>libfn.o</code>依然没有<strong>被之前链接的<code>*.o</code>引用到，也就是没有任何符号在<code>unresolved symbol table</code>中</strong>，所以<code>libfn.o</code>也被忽略。</p>

<h2>一些实践</h2>

<p>在实际开发中还会遇到一些静态库相关的问题。</p>

<h3>链接顺序问题</h3>

<p>前面的例子已经展示了这个问题。<strong>调整库的链接顺序</strong>可以解决大部分问题，但当静态库之间存在环形依赖时，则无法通过调整顺序来解决。</p>

<h4>-whole-archive</h4>

<p><code>-whole-archive</code>选项告诉链接器把静态库中的所有<code>.o</code>都进行链接，针对以上例子：</p>

<pre><code>$ g++ -o test -L. test.o -Wl,--whole-archive -lfn main.o -Wl,--no-whole-archive
$ ./test
Lib ctor
Test ctor
main
sum: 5
</code></pre>

<p>连<code>lib.o</code>也被链接了进来。<em><code>-Wl</code>选项告诉gcc将其作为链接器参数传入；之所以在命令行结尾加上<code>--no-whole-archive</code>是为了告诉编译器不要链接gcc默认的库</em></p>

<p>可以看出这个方法还是有点暴力了。</p>

<h4>&#8211;start-group</h4>

<p>格式为：</p>

<pre><code>--start-group archives --end-group
</code></pre>

<p>位于<code>--start-group</code>  <code>--end-group</code>中的所有静态库将被反复搜索，而不是默认的只搜索一次，直到不再有新的<code>unresolved symbol</code>产生为止。也就是说，出现在这里的<code>.o</code>如果发现有<code>unresolved symbol</code>，则可能回到之前的静态库中继续搜索。</p>

<pre><code>$ g++ -o test -L. test.o -Wl,--start-group -lfn main.o -Wl,--end-group
$ ./test
Test ctor
main
sum: 5
</code></pre>

<p>查看<code>ldd</code>关于该参数的man page还可以一窥链接过程的细节：</p>

<blockquote><p>The specified archives are searched repeatedly until no new undefined references are created. Normally, an archive is searched only once in the order that it is specified on the command line. If a symbol in that archive is needed to resolve an undefined symbol referred to by an object in an archive that appears later on the command line, the linker would not be able to resolve that reference. By grouping the archives, they all be searched repeatedly until all possible references are resolved.</p></blockquote>

<h3>嵌套静态库</h3>

<p>由于<code>ar</code>创建静态库时本质上只是对文件进行打包，所以甚至可以创建一个嵌套的静态库，从而测试链接器是否会递归处理静态库中的<code>.o</code>：</p>

<pre><code>$ ar -r libfn.a libfn.o
$ ar -r liboutfn.a libfn.a lib.o
$ g++ -o test -L. test.o main.o -loutfn
main.o: In function `main':
main.cpp:(.text+0x19): undefined reference to `sum(int, int)'
collect2: ld returned 1 exit status
</code></pre>

<p><strong>可见链接器并不会递归处理静态库中的文件</strong></p>

<p>之所以要提到嵌套静态库这个问题，是因为我发现很多时候我们喜欢为一个静态库工程链接其他静态库。当然，这里的链接并非真正的链接（仅是打包），这个过程当然可以聪明到将其他静态库里的<code>.o</code>提取出来然后打包到新的静态库。</p>

<p>如果我们使用的是类似<a href="http://www.scons.org/">scons</a>这种封装更高的依赖项管理工具，那么它是否会这样干呢？</p>

<p>基于之前的例子，我们使用scons来创建<code>liboutfn.a</code>：</p>

<pre><code># Sconstruct
StaticLibrary('liboutfn.a', ['libfn.a', 'lib.o'])
</code></pre>

<p>使用文本编辑器打开<code>liboutfn.a</code>就可以看到其内容，或者使用：</p>

<pre><code>$ ar -tv liboutfn.a
rw-r--r-- 60833/100   1474 Sep 14 02:59 2014 libfn.a
rw-r--r-- 60833/100   2448 Sep 14 02:16 2014 lib.o
</code></pre>

<p>可见scons也只是单纯地打包。<strong>所以，在scons中构建一个静态库时，再<code>链接</code>其他静态库是没有意义的</strong></p>

<h2>参考文档</h2>

<ul>
<li><a href="http://en.wikipedia.org/wiki/Ar_%28Unix%29#File_format_details">ar (Unix)</a></li>
<li><a href="http://linux.die.net/man/1/ld">ld man page</a></li>
<li><a href="http://wen00072-blog.logdown.com/posts/188339-study-on-the-gnu-ld">GNU ld初探</a></li>
<li><a href="http://eli.thegreenplace.net/2013/07/09/library-order-in-static-linking/">Library order in static linking</a></li>
<li><a href="http://www.linuxjournal.com/article/6463?page=0,1">Linkers and Loaders</a></li>
<li><a href="http://www.scons.org/doc/0.96.1/HTML/scons-user/c549.html">scons Building and Linking with Libraries</a></li>
</ul>


<p class="post-footer">
            原文地址：
            <a href="http://codemacro.com/2014/09/15/inside-static-library/">http://codemacro.com/2014/09/15/inside-static-library/</a><br />
            written by <a href="http://codemacro.com">Kevin Lynx</a>
            &nbsp;posted at <a href="http://codemacro.com">http://codemacro.com</a>
            </p>

</div><img src ="http://www.cppblog.com/kevinlynx/aggbug/208320.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/kevinlynx/" target="_blank">Kevin Lynx</a> 2014-09-15 22:47 <a href="http://www.cppblog.com/kevinlynx/archive/2014/09/15/208320.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C/C++中手动获取调用堆栈</title><link>http://www.cppblog.com/kevinlynx/archive/2014/09/02/208210.html</link><dc:creator>Kevin Lynx</dc:creator><author>Kevin Lynx</author><pubDate>Tue, 02 Sep 2014 14:14:00 GMT</pubDate><guid>http://www.cppblog.com/kevinlynx/archive/2014/09/02/208210.html</guid><wfw:comment>http://www.cppblog.com/kevinlynx/comments/208210.html</wfw:comment><comments>http://www.cppblog.com/kevinlynx/archive/2014/09/02/208210.html#Feedback</comments><slash:comments>3</slash:comments><wfw:commentRss>http://www.cppblog.com/kevinlynx/comments/commentRss/208210.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/kevinlynx/services/trackbacks/208210.html</trackback:ping><description><![CDATA[<div class="entry-content"><p>当我们的程序core掉之后，如果能获取到core时的函数调用堆栈将非常有利于定位问题。在Windows下可以使用<a href="http://blog.csdn.net/starlee/article/details/6630816">SEH机制</a>；在Linux下通过gdb使用coredump文件即可。</p>
<p>但有时候由于某些错误导致堆栈被破坏，发生拿不到调用堆栈的情况。</p>
<p>一些基础预备知识本文不再详述，可以参考以下文章：</p>
<ul>
<li><a href="http://hutaow.com/blog/2013/10/15/dump-stack/">函数调用栈的获取原理分析</a></li>
<li><a href="http://www.findfunaax.com/notes/file/262">寄存器、函数调用与栈帧</a></li>
</ul>
<p>需要知道的信息：</p>
<ul>
<li>函数调用对应的<code>call</code>指令本质上是先压入下一条指令的地址到堆栈，然后跳转到目标函数地址</li>
<li>函数返回指令<code>ret</code>则是从堆栈取出一个地址，然后跳转到该地址</li>
<li>EBP寄存器始终指向当前执行函数相关信息（局部变量）所在栈中的位置，ESP则始终指向栈顶</li>
<li>每一个函数入口都会保存调用者的EBP值，在出口处都会重设EBP值，从而实现函数调用的现场保存及现场恢复</li>
<li>64位机器增加了不少寄存器，从而使得函数调用的参数大部分时候可以通过寄存器传递；同时寄存器名字发生改变，例如EBP变为RBP</li>
</ul>
<p>在函数调用中堆栈的情况可用下图说明：</p>
<!-- more -->
<p><img src="http://codemacro.com/assets/res/stack_frame/stack_frame.png" alt="" /></p>
<p>将代码对应起来：</p>
<div class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="kt">    void</span> <span class="nf">g</span><span class="p">()</span> <span class="p">{</span>
        <span class="kt">int</span> <span class="o">*</span><span class="n">p</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
        <span class="kt">long</span> <span class="n">a</span> <span class="o">=</span> <span class="mh">0x1234</span><span class="p">;</span>
        <span class="n">printf</span><span class="p">(</span><span class="s">"%p %x</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">a</span><span class="p">,</span> <span class="n">a</span><span class="p">);</span>
        <span class="n">printf</span><span class="p">(</span><span class="s">"%p %x</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">p</span><span class="p">,</span> <span class="n">p</span><span class="p">);</span>
        <span class="n">f</span><span class="p">();</span>
        <span class="o">*</span><span class="n">p</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="kt">void</span> <span class="nf">b</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">printf</span><span class="p">(</span><span class="s">"%p %p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">argc</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">argv</span><span class="p">);</span>
        <span class="n">g</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">b</span><span class="p">(</span><span class="n">argc</span><span class="p">,</span> <span class="n">argv</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>在函数<code>g()</code>中断点，看看堆栈中的内容(64位机器)：</p>
<pre><code>(gdb) p $rbp
$2 = (void *) 0x7fffffffe370
(gdb) p &amp;p
$3 = (int **) 0x7fffffffe368
(gdb) p $rsp
$4 = (void *) 0x7fffffffe360
(gdb) x/8ag $rbp-16
0x7fffffffe360: 0x1234  0x0
0x7fffffffe370: 0x7fffffffe390  0x400631 &lt;b(int, char**)+43&gt;
0x7fffffffe380: 0x7fffffffe498  0x1a561cbc0
0x7fffffffe390: 0x7fffffffe3b0  0x40064f &lt;main(int, char**)+27&gt;
</code></pre>
<p>对应的堆栈图：</p>
<p><img src="http://codemacro.com/assets/res/stack_frame/stack_frame_ex.png" alt="" /></p>
<p>可以看看例子中<code>0x400631 &lt;b(int, char**)+43&gt;</code>和<code>0x40064f &lt;main(int, char**)+27&gt;</code>中的代码：</p>
<pre><code>(gdb) disassemble 0x400631
...
0x0000000000400627 &lt;b(int, char**)+33&gt;: callq  0x400468 &lt;printf@plt&gt;
0x000000000040062c &lt;b(int, char**)+38&gt;: callq  0x4005ae &lt;g()&gt;
0x0000000000400631 &lt;b(int, char**)+43&gt;: leaveq                           # call的下一条指令
...
(gdb) disassemble 0x40064f
... 
0x000000000040063f &lt;main(int, char**)+11&gt;:      mov    %rsi,-0x10(%rbp)
0x0000000000400643 &lt;main(int, char**)+15&gt;:      mov    -0x10(%rbp),%rsi
0x0000000000400647 &lt;main(int, char**)+19&gt;:      mov    -0x4(%rbp),%edi
0x000000000040064a &lt;main(int, char**)+22&gt;:      callq  0x400606 &lt;b(int, char**)&gt;
0x000000000040064f &lt;main(int, char**)+27&gt;:      mov    $0x0,%eax         # call的下一条指令
...
</code></pre>
<p>顺带一提，每个函数入口和出口，对应的设置RBP代码为：</p>
<pre><code>(gdb) disassemble g
...
0x00000000004005ae &lt;g()+0&gt;:     push   %rbp               # 保存调用者的RBP到堆栈
0x00000000004005af &lt;g()+1&gt;:     mov    %rsp,%rbp          # 设置自己的RBP
...
0x0000000000400603 &lt;g()+85&gt;:    leaveq                    # 等同于：movq %rbp, %rsp
                                                          #         popq %rbp
0x0000000000400604 &lt;g()+86&gt;:    retq                      
</code></pre>
<p>由以上可见，<strong>通过当前的RSP或RBP就可以找到调用堆栈中所有函数的RBP；找到了RBP就可以找到函数地址</strong>。因为，任何时候的RBP指向的堆栈位置就是上一个函数的RBP；而任何时候RBP所在堆栈中的前一个位置就是函数返回地址。</p>
<p>由此我们可以自己构建一个导致gdb无法取得调用堆栈的例子：</p>
<div class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="kt">    void</span> <span class="nf">f</span><span class="p">()</span> <span class="p">{</span>
        <span class="kt">long</span> <span class="o">*</span><span class="n">p</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
        <span class="n">p</span> <span class="o">=</span> <span class="p">(</span><span class="kt">long</span><span class="o">*</span><span class="p">)</span> <span class="p">(</span><span class="o">&amp;</span><span class="n">p</span> <span class="o">+</span> <span class="mi">1</span><span class="p">);</span> <span class="c1">// 取得g()的RBP</span>
        <span class="o">*</span><span class="n">p</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>  <span class="c1">// 破坏g()的RBP</span>
    <span class="p">}</span>
    <span class="kt">void</span> <span class="nf">g</span><span class="p">()</span> <span class="p">{</span>
        <span class="kt">int</span> <span class="o">*</span><span class="n">p</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
        <span class="kt">long</span> <span class="n">a</span> <span class="o">=</span> <span class="mh">0x1234</span><span class="p">;</span>
        <span class="n">printf</span><span class="p">(</span><span class="s">"%p %x</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">a</span><span class="p">,</span> <span class="n">a</span><span class="p">);</span>
        <span class="n">printf</span><span class="p">(</span><span class="s">"%p %x</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">p</span><span class="p">,</span> <span class="n">p</span><span class="p">);</span>
        <span class="n">f</span><span class="p">();</span>
        <span class="o">*</span><span class="n">p</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="c1">// 写0地址导致一次core</span>
    <span class="p">}</span>
    <span class="kt">void</span> <span class="nf">b</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">printf</span><span class="p">(</span><span class="s">"%p %p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">argc</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">argv</span><span class="p">);</span>
        <span class="n">g</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">b</span><span class="p">(</span><span class="n">argc</span><span class="p">,</span> <span class="n">argv</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>使用gdb运行该程序：</p>
<pre><code>Program received signal SIGSEGV, Segmentation fault.
g () at ebp.c:37
37          *p = 1;
(gdb) bt
Cannot access memory at address 0x8
(gdb) p $rbp
$1 = (void *) 0x0
</code></pre>
<p><code>bt</code>无法获取堆栈，在函数<code>g()</code>中RBP被改写为0，gdb从0偏移一个地址长度即0x8，尝试从0x8内存位置获取函数地址，然后提示<code>Cannot access memory at address 0x8</code>。</p>
<p><strong>RBP出现了问题，我们就可以通过RSP来手动获取调用堆栈。</strong>因为RSP是不会被破坏的，要通过RSP获取调用堆栈则需要偏移一些局部变量所占的空间：</p>
<pre><code>(gdb) p $rsp
$2 = (void *) 0x7fffffffe360
(gdb) x/8ag $rsp+16             # g()中局部变量占16字节
0x7fffffffe370: 0x7fffffffe390  0x400631 &lt;b(int, char**)+43&gt;
0x7fffffffe380: 0x7fffffffe498  0x1a561cbc0
0x7fffffffe390: 0x7fffffffe3b0  0x40064f &lt;main(int, char**)+27&gt;
0x7fffffffe3a0: 0x7fffffffe498  0x100000000
</code></pre>
<p>基于以上就可以手工找到调用堆栈：</p>
<pre><code>g()
0x400631 &lt;b(int, char**)+43&gt;
0x40064f &lt;main(int, char**)+27&gt;
</code></pre>
<p>上面的例子本质上也是破坏堆栈，并且仅仅破坏了保存了的RBP。在实际情况中，堆栈可能会被破坏得更多，则可能导致手动定位也较困难。</p>
<p>堆栈被破坏还可能导致更多的问题，例如覆盖了函数返回地址，则会导致RIP错误；例如堆栈的不平衡。导致堆栈被破坏的原因也有很多，例如局部数组越界；<a href="http://codemacro.com/2013/08/15/debug-esp-bug/">delete/free栈上对象等</a>。</p>
<h2>omit-frame-pointer</h2>
<p>使用RBP获取调用堆栈相对比较容易。但现在编译器都可以设置不使用RBP(gcc使用-fomit-frame-pointer，msvc使用/Oy)，对于函数而言不设置其RBP意味着可以节省若干条指令。在函数内部则完全使用RSP的偏移来定位局部变量，包括嵌套作用域里的局部变量，即使程序实际运行时不会进入这个作用域。</p>
<p>例如：</p>
<div class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="kt">    void</span> <span class="nf">f2</span><span class="p">()</span> <span class="p">{</span>
        <span class="kt">int</span> <span class="n">a</span> <span class="o">=</span> <span class="mh">0x1234</span><span class="p">;</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">a</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
            <span class="kt">int</span> <span class="n">b</span> <span class="o">=</span> <span class="mh">0xff</span><span class="p">;</span>
            <span class="n">b</span> <span class="o">=</span> <span class="n">a</span><span class="p">;</span>
        <span class="p">}</span>
    <span class="p">}</span></code></pre></div>
<p>gcc中使用<code>-fomit-frame-pointer</code>生成的代码为：</p>
<pre><code>(gdb) disassemble f2
Dump of assembler code for function f2:
0x00000000004004a5 &lt;f2+0&gt;:      movl   $0x1234,-0x8(%rsp)    # int a = 0x1234
0x00000000004004ad &lt;f2+8&gt;:      cmpl   $0x0,-0x8(%rsp)       
0x00000000004004b2 &lt;f2+13&gt;:     jle    0x4004c4 &lt;f2+31&gt;      
0x00000000004004b4 &lt;f2+15&gt;:     movl   $0xff,-0x4(%rsp)      # int b = 0xff
0x00000000004004bc &lt;f2+23&gt;:     mov    -0x8(%rsp),%eax
0x00000000004004c0 &lt;f2+27&gt;:     mov    %eax,-0x4(%rsp)
0x00000000004004c4 &lt;f2+31&gt;:     retq
</code></pre>
<p>可以发现<code>f2()</code>没有操作<code>RBP</code>之类的指令了。</p>
<p class="post-footer">
            原文地址：
            <a href="http://codemacro.com/2014/09/02/stack-frame/">http://codemacro.com/2014/09/02/stack-frame/</a><br />
            written by <a href="http://codemacro.com">Kevin Lynx</a>
            &nbsp;posted at <a href="http://codemacro.com">http://codemacro.com</a>
            </p>
</div><img src ="http://www.cppblog.com/kevinlynx/aggbug/208210.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/kevinlynx/" target="_blank">Kevin Lynx</a> 2014-09-02 22:14 <a href="http://www.cppblog.com/kevinlynx/archive/2014/09/02/208210.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>基于protobuf的RPC实现</title><link>http://www.cppblog.com/kevinlynx/archive/2014/08/31/208189.html</link><dc:creator>Kevin Lynx</dc:creator><author>Kevin Lynx</author><pubDate>Sun, 31 Aug 2014 11:40:00 GMT</pubDate><guid>http://www.cppblog.com/kevinlynx/archive/2014/08/31/208189.html</guid><wfw:comment>http://www.cppblog.com/kevinlynx/comments/208189.html</wfw:comment><comments>http://www.cppblog.com/kevinlynx/archive/2014/08/31/208189.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/kevinlynx/comments/commentRss/208189.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/kevinlynx/services/trackbacks/208189.html</trackback:ping><description><![CDATA[<div class="entry-content">
<p>可以对照<a href="http://www.codedump.info/?p=169">使用google protobuf RPC实现echo service</a>一文看，细节本文不再描述。</p>

<p>google protobuf只负责消息的打包和解包，并不包含RPC的实现，但其包含了RPC的定义。假设有下面的RPC定义：</p>

<div class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="n">service</span> <span class="n">MyService</span> <span class="p">{</span>
        <span class="n">rpc</span> <span class="n">Echo</span><span class="p">(</span><span class="n">EchoReqMsg</span><span class="p">)</span> <span class="n">returns</span><span class="p">(</span><span class="n">EchoRespMsg</span><span class="p">)</span> 
    <span class="p">}</span></code></pre></div>


<p>那么要实现这个RPC需要最少做哪些事？总结起来需要完成以下几步：</p>

<h2>客户端</h2>

<p>RPC客户端需要实现<code>google::protobuf::RpcChannel</code>。主要实现<code>RpcChannel::CallMethod</code>接口。客户端调用任何一个RPC接口，最终都是调用到<code>CallMethod</code>。这个函数的典型实现就是将RPC调用参数序列化，然后投递给网络模块进行发送。</p>

<div class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="kt">void</span> <span class="nf">CallMethod</span><span class="p">(</span><span class="k">const</span> <span class="o">::</span><span class="n">google</span><span class="o">::</span><span class="n">protobuf</span><span class="o">::</span><span class="n">MethodDescriptor</span><span class="o">*</span> <span class="n">method</span><span class="p">,</span>
                  <span class="o">::</span><span class="n">google</span><span class="o">::</span><span class="n">protobuf</span><span class="o">::</span><span class="n">RpcController</span><span class="o">*</span> <span class="n">controller</span><span class="p">,</span>
                  <span class="k">const</span> <span class="o">::</span><span class="n">google</span><span class="o">::</span><span class="n">protobuf</span><span class="o">::</span><span class="n">Message</span><span class="o">*</span> <span class="n">request</span><span class="p">,</span>
                  <span class="o">::</span><span class="n">google</span><span class="o">::</span><span class="n">protobuf</span><span class="o">::</span><span class="n">Message</span><span class="o">*</span> <span class="n">response</span><span class="p">,</span>
                  <span class="o">::</span><span class="n">google</span><span class="o">::</span><span class="n">protobuf</span><span class="o">::</span><span class="n">Closure</span><span class="o">*</span> <span class="n">done</span><span class="p">)</span> <span class="p">{</span>
        <span class="p">...</span>
        <span class="n">DataBufferOutputStream</span> <span class="n">outputStream</span><span class="p">(...)</span> <span class="c1">// 取决于你使用的网络实现</span>
        <span class="n">request</span><span class="o">-&gt;</span><span class="n">SerializeToZeroCopyStream</span><span class="p">(</span><span class="o">&amp;</span><span class="n">outputStream</span><span class="p">);</span>
        <span class="n">_connection</span><span class="o">-&gt;</span><span class="n">postData</span><span class="p">(</span><span class="n">outputStream</span><span class="p">.</span><span class="n">getData</span><span class="p">(),</span> <span class="p">...</span>
        <span class="p">...</span>
    <span class="p">}</span></code></pre></div>




<!-- more -->


<h2>服务端</h2>

<p>服务端首先需要实现RPC接口，直接实现<code>MyService</code>中定义的接口：</p>

<div class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="k">class</span> <span class="nc">MyServiceImpl</span> <span class="o">:</span> <span class="k">public</span> <span class="n">MyService</span> <span class="p">{</span>
        <span class="k">virtual</span> <span class="kt">void</span> <span class="n">Echo</span><span class="p">(</span><span class="o">::</span><span class="n">google</span><span class="o">::</span><span class="n">protobuf</span><span class="o">::</span><span class="n">RpcController</span><span class="o">*</span> <span class="n">controller</span><span class="p">,</span>
            <span class="k">const</span> <span class="n">EchoReqMsg</span><span class="o">*</span> <span class="n">request</span><span class="p">,</span>
            <span class="n">EchoRespMsg</span><span class="o">*</span> <span class="n">response</span><span class="p">,</span>
            <span class="o">::</span><span class="n">google</span><span class="o">::</span><span class="n">protobuf</span><span class="o">::</span><span class="n">Closure</span><span class="o">*</span> <span class="n">done</span><span class="p">)</span> <span class="p">{</span>
            <span class="p">...</span>
            <span class="n">done</span><span class="o">-&gt;</span><span class="n">Run</span><span class="p">();</span>
        <span class="p">}</span>
    <span class="p">}</span></code></pre></div>


<h2>标示service&amp;method</h2>

<p>基于以上，可以看出服务端根本不知道客户端想要调用哪一个RPC接口。从服务器接收到网络消息，到调用到<code>MyServiceImpl::Echo</code>还有很大一段距离。</p>

<p>解决方法就是在网络消息中带上RPC接口标识。这个标识可以直接带上service name和method name，但这种实现导致网络消息太大。另一种实现是基于service name和method name生成一个哈希值，因为接口不会太多，所以较容易找到基本不冲突的字符串哈希算法。</p>

<p>无论哪种方法，服务器是肯定需要建立RPC接口标识到protobuf service对象的映射的。</p>

<p>这里提供第三种方法：基于option的方法。</p>

<p>protobuf中option机制类似于这样一种机制：service&amp;method被视为一个对象，其有很多属性，属性包含内置的，以及用户扩展的。用户扩展的就是option。每一个属性有一个值。protobuf提供访问service&amp;method这些属性的接口。</p>

<p>首先扩展service&amp;method的属性，以下定义这些属性的key：</p>

<div class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="n">extend</span> <span class="n">google</span><span class="p">.</span><span class="n">protobuf</span><span class="p">.</span><span class="n">ServiceOptions</span> <span class="p">{</span>
      <span class="n">required</span> <span class="n">uint32</span> <span class="n">global_service_id</span> <span class="o">=</span> <span class="mi">1000</span><span class="p">;</span> 
    <span class="p">}</span>
    <span class="n">extend</span> <span class="n">google</span><span class="p">.</span><span class="n">protobuf</span><span class="p">.</span><span class="n">MethodOptions</span> <span class="p">{</span>
      <span class="n">required</span> <span class="n">uint32</span> <span class="n">local_method_id</span> <span class="o">=</span> <span class="mi">1000</span><span class="p">;</span>
    <span class="p">}</span></code></pre></div>


<p>应用层定义service&amp;method时可以指定以上key的值：</p>

<div class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="n">service</span> <span class="n">MyService</span>
    <span class="p">{</span>
        <span class="n">option</span> <span class="p">(</span><span class="n">arpc</span><span class="p">.</span><span class="n">global_service_id</span><span class="p">)</span> <span class="o">=</span> <span class="mi">2302</span><span class="p">;</span> 

        <span class="n">rpc</span> <span class="nf">Echo</span><span class="p">(</span><span class="n">EchoReqMsg</span><span class="p">)</span> <span class="n">returns</span><span class="p">(</span><span class="n">EchoRespMsg</span><span class="p">)</span> 
        <span class="p">{</span>
            <span class="n">option</span> <span class="p">(</span><span class="n">arpc</span><span class="p">.</span><span class="n">local_method_id</span><span class="p">)</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="n">rpc</span> <span class="nf">Echo_2</span><span class="p">(</span><span class="n">EchoReqMsg</span><span class="p">)</span> <span class="n">returns</span><span class="p">(</span><span class="n">EchoRespMsg</span><span class="p">)</span> 
        <span class="p">{</span>
            <span class="n">option</span> <span class="p">(</span><span class="n">arpc</span><span class="p">.</span><span class="n">local_method_id</span><span class="p">)</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="p">...</span>
    <span class="p">}</span></code></pre></div>


<p>以上相当于在整个应用中，每个service都被赋予了唯一的id，单个service中的method也有唯一的id。</p>

<p>然后可以通过protobuf取出以上属性值：</p>

<div class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="kt">void</span> <span class="nf">CallMethod</span><span class="p">(</span><span class="k">const</span> <span class="o">::</span><span class="n">google</span><span class="o">::</span><span class="n">protobuf</span><span class="o">::</span><span class="n">MethodDescriptor</span><span class="o">*</span> <span class="n">method</span><span class="p">,</span>
                  <span class="o">::</span><span class="n">google</span><span class="o">::</span><span class="n">protobuf</span><span class="o">::</span><span class="n">RpcController</span><span class="o">*</span> <span class="n">controller</span><span class="p">,</span>
                  <span class="k">const</span> <span class="o">::</span><span class="n">google</span><span class="o">::</span><span class="n">protobuf</span><span class="o">::</span><span class="n">Message</span><span class="o">*</span> <span class="n">request</span><span class="p">,</span>
                  <span class="o">::</span><span class="n">google</span><span class="o">::</span><span class="n">protobuf</span><span class="o">::</span><span class="n">Message</span><span class="o">*</span> <span class="n">response</span><span class="p">,</span>
                  <span class="o">::</span><span class="n">google</span><span class="o">::</span><span class="n">protobuf</span><span class="o">::</span><span class="n">Closure</span><span class="o">*</span> <span class="n">done</span><span class="p">)</span> <span class="p">{</span>
        <span class="p">...</span>
        <span class="n">google</span><span class="o">::</span><span class="n">protobuf</span><span class="o">::</span><span class="n">ServiceDescriptor</span> <span class="o">*</span><span class="n">service</span> <span class="o">=</span> <span class="n">method</span><span class="o">-&gt;</span><span class="n">service</span><span class="p">();</span>
        <span class="kt">uint32_t</span> <span class="n">serviceId</span> <span class="o">=</span> <span class="p">(</span><span class="kt">uint32_t</span><span class="p">)(</span><span class="n">service</span><span class="o">-&gt;</span><span class="n">options</span><span class="p">().</span><span class="n">GetExtension</span><span class="p">(</span><span class="n">global_service_id</span><span class="p">));</span>
        <span class="kt">uint32_t</span> <span class="n">methodId</span> <span class="o">=</span> <span class="p">(</span><span class="kt">uint32_t</span><span class="p">)(</span><span class="n">method</span><span class="o">-&gt;</span><span class="n">options</span><span class="p">().</span><span class="n">GetExtension</span><span class="p">(</span><span class="n">local_method_id</span><span class="p">));</span>
        <span class="p">...</span>
    <span class="p">}</span></code></pre></div>


<p>考虑到<code>serviceId</code> <code>methodId</code>的范围，可以直接打包到一个32位整数里：</p>

<pre><code>uint32_t ret = (serviceId &lt;&lt; 16) | methodId;
</code></pre>

<p>然后就可以把这个值作为网络消息头的一部分发送。</p>

<p>当然服务器端是需要建立这个标识值到service的映射的：</p>

<div class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="kt">bool</span> <span class="n">MyRPCServer</span><span class="o">::</span><span class="n">registerService</span><span class="p">(</span><span class="n">google</span><span class="o">::</span><span class="n">protobuf</span><span class="o">::</span><span class="n">Service</span> <span class="o">*</span><span class="n">rpcService</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">const</span> <span class="n">google</span><span class="o">::</span><span class="n">protobuf</span><span class="o">::</span><span class="n">ServiceDescriptor</span> <span class="o">=</span> <span class="n">rpcService</span><span class="o">-&gt;</span><span class="n">GetDescriptor</span><span class="p">();</span>
        <span class="kt">int</span> <span class="n">methodCnt</span> <span class="o">=</span> <span class="n">pSerDes</span><span class="o">-&gt;</span><span class="n">method_count</span><span class="p">();</span>

        <span class="k">for</span> <span class="p">(</span><span class="kt">int</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="n">methodCnt</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">google</span><span class="o">::</span><span class="n">protobuf</span><span class="o">::</span><span class="n">MethodDescriptor</span> <span class="o">*</span><span class="n">pMethodDes</span> <span class="o">=</span> <span class="n">pSerDes</span><span class="o">-&gt;</span><span class="n">method</span><span class="p">(</span><span class="n">i</span><span class="p">);</span>
            <span class="kt">uint32_t</span> <span class="n">rpcCode</span> <span class="o">=</span> <span class="n">PacketCodeBuilder</span><span class="p">()(</span><span class="n">pMethodDes</span><span class="p">);</span> <span class="c1">// 计算出映射值</span>
            <span class="n">_rpcCallMap</span><span class="p">[</span><span class="n">rpcCode</span><span class="p">]</span> <span class="o">=</span> <span class="n">make_pair</span><span class="p">(</span><span class="n">rpcService</span><span class="p">,</span> <span class="n">pMethodDes</span><span class="p">);</span> <span class="c1">// 建立映射</span>
        <span class="p">}</span>
        <span class="k">return</span> <span class="nb">true</span><span class="p">;</span>
    <span class="p">}</span></code></pre></div>


<p>服务端收到RPC调用后，取出这个标识值，然后再从<code>_rpcCallMap</code>中取出对应的service和method，最后进行调用：</p>

<div class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="n">google</span><span class="o">::</span><span class="n">protobuf</span><span class="o">::</span><span class="n">Message</span><span class="o">*</span> <span class="n">response</span> <span class="o">=</span> <span class="n">_pService</span><span class="o">-&gt;</span><span class="n">GetResponsePrototype</span><span class="p">(</span><span class="n">_pMethodDes</span><span class="p">).</span><span class="n">New</span><span class="p">();</span>
    <span class="c1">// 用于回应的closure</span>
    <span class="n">RPCServerClosure</span> <span class="o">*</span><span class="n">pClosure</span> <span class="o">=</span> <span class="k">new</span> <span class="p">(</span><span class="n">nothrow</span><span class="p">)</span> <span class="n">RPCServerClosure</span><span class="p">(</span> 
            <span class="n">_channelId</span><span class="p">,</span> <span class="n">_pConnection</span><span class="p">,</span> <span class="n">_pReqMsg</span><span class="p">,</span> <span class="n">pResMsg</span><span class="p">,</span> <span class="n">_messageCodec</span><span class="p">,</span> <span class="n">_version</span><span class="p">);</span>
    <span class="n">RPCController</span> <span class="o">*</span><span class="n">pController</span> <span class="o">=</span> <span class="n">pClosure</span><span class="o">-&gt;</span><span class="n">GetRpcController</span><span class="p">();</span>
    <span class="p">...</span>
    <span class="c1">// protobuf 生成的CallMethod，会自动调用到Echo接口</span>
    <span class="n">_pService</span><span class="o">-&gt;</span><span class="n">CallMethod</span><span class="p">(</span><span class="n">_pMethodDes</span><span class="p">,</span> <span class="n">pController</span><span class="p">,</span> <span class="n">_pReqMsg</span><span class="p">,</span> <span class="n">pResMsg</span><span class="p">,</span> <span class="n">pClosure</span><span class="p">);</span></code></pre></div>


<h2>参考</h2>

<ul>
<li><a href="http://www.codedump.info/?p=169">使用google protobuf RPC实现echo service</a></li>
<li><a href="https://developers.google.com/protocol-buffers/docs/proto?hl=zh-cn#extensions">protobuf extensions</a></li>
<li><a href="https://developers.google.com/protocol-buffers/docs/proto#services">protobuf service</a></li>
<li><a href="https://developers.google.com/protocol-buffers/docs/reference/cpp/google.protobuf.descriptor#MethodDescriptor.options.details">protobuf options</a></li>
</ul>

</div><img src ="http://www.cppblog.com/kevinlynx/aggbug/208189.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/kevinlynx/" target="_blank">Kevin Lynx</a> 2014-08-31 19:40 <a href="http://www.cppblog.com/kevinlynx/archive/2014/08/31/208189.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Muduo源码阅读</title><link>http://www.cppblog.com/kevinlynx/archive/2014/05/04/206817.html</link><dc:creator>Kevin Lynx</dc:creator><author>Kevin Lynx</author><pubDate>Sun, 04 May 2014 10:22:00 GMT</pubDate><guid>http://www.cppblog.com/kevinlynx/archive/2014/05/04/206817.html</guid><wfw:comment>http://www.cppblog.com/kevinlynx/comments/206817.html</wfw:comment><comments>http://www.cppblog.com/kevinlynx/archive/2014/05/04/206817.html#Feedback</comments><slash:comments>3</slash:comments><wfw:commentRss>http://www.cppblog.com/kevinlynx/comments/commentRss/206817.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/kevinlynx/services/trackbacks/206817.html</trackback:ping><description><![CDATA[<div class="entry-content">
<p>最近简单读了下<a href="http://blog.csdn.net/solstice/article/details/5848547">muduo</a>的源码，本文对其主要实现/结构简单总结下。</p>

<p>muduo的主要源码位于net文件夹下，base文件夹是一些基础代码，不影响理解网络部分的实现。muduo主要类包括：</p>

<ul>
<li>EventLoop</li>
<li>Channel</li>
<li>Poller</li>
<li>TcpConnection</li>
<li>TcpClient</li>
<li>TcpServer</li>
<li>Connector</li>
<li>Acceptor</li>
<li>EventLoopThread</li>
<li>EventLoopThreadPool</li>
</ul>
<p>其中，Poller（及其实现类）包装了Poll/EPoll，封装了OS针对设备(fd)的操作；Channel是设备fd的包装，在muduo中主要包装socket；TcpConnection抽象一个TCP连接，无论是客户端还是服务器只要建立了网络连接就会使用TcpConnection；TcpClient/TcpServer分别抽象TCP客户端和服务器；Connector/Acceptor分别包装TCP客户端和服务器的建立连接/接受连接；EventLoop是一个主控类，是一个事件发生器，它驱动Poller产生/发现事件，然后将事件派发到Channel处理；EventLoopThread是一个带有EventLoop的线程；EventLoopThreadPool自然是一个EventLoopThread的资源池，维护一堆EventLoopThread。</p>

<p>阅读库源码时可以从库的接口层着手，看看关键功能是如何实现的。对于muduo而言，可以从TcpServer/TcpClient/EventLoop/TcpConnection这几个类着手。接下来看看主要功能的实现：</p>

<!-- more -->


<h2>建立连接</h2>

<div class="highlight">
<pre><code class="c">    <span class="n">TcpClient</span><span class="o">::</span><span class="n">connect</span> 
        <span class="o">-&gt;</span> <span class="n">Connector</span><span class="o">::</span><span class="n">start</span> 
            <span class="o">-&gt;</span> <span class="n">EventLoop</span><span class="o">::</span><span class="n">runInLoop</span><span class="p">(</span><span class="n">Connector</span><span class="o">::</span><span class="n">startInLoop</span><span class="p">...</span>
            <span class="o">-&gt;</span> <span class="n">Connector</span><span class="o">::</span><span class="n">connect</span>             
</code></pre>
</div>


<p>EventLoop::runInLoop接口用于在this所在的线程运行某个函数，这个后面看下EventLoop的实现就可以了解。 网络连接的最终建立是在Connector::connect中实现，建立连接之后会创建一个Channel来代表这个socket，并且绑定事件监听接口。最后最重要的是，调用<code>Channel::enableWriting</code>。<code>Channel</code>有一系列的enableXX接口，这些接口用于标识自己关心某IO事件。后面会看到他们的实现。</p>

<p>Connector监听的主要事件无非就是连接已建立，用它监听读数据/写数据事件也不符合设计。TcpConnection才是做这种事的。</p>

<h2>客户端收发数据</h2>

<p>当Connector发现连接真正建立好后，会回调到<code>TcpClient::newConnection</code>，在TcpClient构造函数中：</p>

<div class="highlight">
<pre><code class="c">    <span class="n">connector_</span><span class="o">-&gt;</span><span class="n">setNewConnectionCallback</span><span class="p">(</span>
      <span class="n">boost</span><span class="o">::</span><span class="n">bind</span><span class="p">(</span><span class="o">&amp;</span><span class="n">TcpClient</span><span class="o">::</span><span class="n">newConnection</span><span class="p">,</span> <span class="n">this</span><span class="p">,</span> <span class="n">_1</span><span class="p">));</span>
</code></pre>
</div>


<p><code>TcpClient::newConnection</code>中创建一个TcpConnection来代表这个连接：</p>

<div class="highlight">
<pre><code class="c">    <span class="n">TcpConnectionPtr</span> <span class="nf">conn</span><span class="p">(</span><span class="n">new</span> <span class="n">TcpConnection</span><span class="p">(</span><span class="n">loop_</span><span class="p">,</span>
                                            <span class="n">connName</span><span class="p">,</span>
                                            <span class="n">sockfd</span><span class="p">,</span>
                                            <span class="n">localAddr</span><span class="p">,</span>
                                            <span class="n">peerAddr</span><span class="p">));</span>

    <span class="n">conn</span><span class="o">-&gt;</span><span class="n">setConnectionCallback</span><span class="p">(</span><span class="n">connectionCallback_</span><span class="p">);</span>
    <span class="n">conn</span><span class="o">-&gt;</span><span class="n">setMessageCallback</span><span class="p">(</span><span class="n">messageCallback_</span><span class="p">);</span>
    <span class="n">conn</span><span class="o">-&gt;</span><span class="n">setWriteCompleteCallback</span><span class="p">(</span><span class="n">writeCompleteCallback_</span><span class="p">);</span>
    <span class="p">...</span>
    <span class="n">conn</span><span class="o">-&gt;</span><span class="n">connectEstablished</span><span class="p">();</span>
</code></pre>
</div>


<p>并同时设置事件回调，以上设置的回调都是应用层（即库的使用者）的接口。每一个TcpConnection都有一个Channel，毕竟每一个网络连接都对应了一个socket fd。在TcpConnection构造函数中创建了一个Channel，并设置事件回调函数。</p>

<p><code>TcpConnection::connectEstablished</code>函数最主要的是通知Channel自己开始关心IO读取事件：</p>

<div class="highlight">
<pre><code class="c">    <span class="kt">void</span> <span class="n">TcpConnection</span><span class="o">::</span><span class="n">connectEstablished</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="p">...</span>
        <span class="n">channel_</span><span class="o">-&gt;</span><span class="n">enableReading</span><span class="p">();</span>
</code></pre>
</div>


<p>这是自此我们看到的第二个<code>Channel::enableXXX</code>接口，这些接口是如何实现关心IO事件的呢？这个后面讲到。</p>

<p>muduo的数据发送都是通过<code>TcpConnection::send</code>完成，这个就是一般网络库中在不使用OS的异步IO情况下的实现：缓存应用层传递过来的数据，在IO设备可写的情况下尽量写入数据。这个主要实现在<code>TcpConnection::sendInLoop</code>中。</p>

<div class="highlight">
<pre><code class="c">    <span class="n">TcpConnection</span><span class="o">::</span><span class="n">sendInLoop</span><span class="p">(....)</span> <span class="p">{</span>
        <span class="p">...</span>
        <span class="c1">// if no thing in output queue, try writing directly</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">channel_</span><span class="o">-&gt;</span><span class="n">isWriting</span><span class="p">()</span> <span class="o">&amp;&amp;</span> <span class="n">outputBuffer_</span><span class="p">.</span><span class="n">readableBytes</span><span class="p">()</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span>  <span class="c1">// 设备可写且没有缓存时立即写入</span>
        <span class="p">{</span> 
            <span class="n">nwrote</span> <span class="o">=</span> <span class="n">sockets</span><span class="o">::</span><span class="n">write</span><span class="p">(</span><span class="n">channel_</span><span class="o">-&gt;</span><span class="n">fd</span><span class="p">(),</span> <span class="n">data</span><span class="p">,</span> <span class="n">len</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="p">...</span>
        <span class="c1">// 否则加入数据到缓存，等待IO可写时再写</span>
        <span class="n">outputBuffer_</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">static_cast</span><span class="o">&lt;</span><span class="k">const</span> <span class="kt">char</span><span class="o">*&gt;</span><span class="p">(</span><span class="n">data</span><span class="p">)</span><span class="o">+</span><span class="n">nwrote</span><span class="p">,</span> <span class="n">remaining</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">channel_</span><span class="o">-&gt;</span><span class="n">isWriting</span><span class="p">())</span>
        <span class="p">{</span>
            <span class="c1">// 注册关心IO写事件，Poller就会对写做检测</span>
            <span class="n">channel_</span><span class="o">-&gt;</span><span class="n">enableWriting</span><span class="p">();</span>
        <span class="p">}</span>
        <span class="p">...</span>     
    <span class="p">}</span>
</code></pre>
</div>


<p>当IO可写时，Channel就会回调<code>TcpConnection::handleWrite</code>（构造函数中注册）</p>

<div class="highlight">
<pre><code class="c">    <span class="kt">void</span> <span class="n">TcpConnection</span><span class="o">::</span><span class="n">handleWrite</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="p">...</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">channel_</span><span class="o">-&gt;</span><span class="n">isWriting</span><span class="p">())</span>
        <span class="p">{</span>
            <span class="kt">ssize_t</span> <span class="n">n</span> <span class="o">=</span> <span class="n">sockets</span><span class="o">::</span><span class="n">write</span><span class="p">(</span><span class="n">channel_</span><span class="o">-&gt;</span><span class="n">fd</span><span class="p">(),</span>
                               <span class="n">outputBuffer_</span><span class="p">.</span><span class="n">peek</span><span class="p">(),</span>
                               <span class="n">outputBuffer_</span><span class="p">.</span><span class="n">readableBytes</span><span class="p">());</span>
</code></pre>
</div>


<p>服务器端的数据收发同客户端机制一致，不同的是连接(TcpConnection)的建立方式不同。</p>

<h2>服务器接收连接</h2>

<p>服务器接收连接的实现在一个网络库中比较重要。muduo中通过Acceptor类来接收连接。在TcpClient中，其Connector通过一个关心Channel可写的事件来通过连接已建立；在Acceptor中则是通过一个Channel可读的事件来表示有新的连接到来：</p>

<div class="highlight">
<pre><code class="c">    <span class="n">Acceptor</span><span class="o">::</span><span class="n">Acceptor</span><span class="p">(....)</span> <span class="p">{</span>
        <span class="p">...</span>
        <span class="n">acceptChannel_</span><span class="p">.</span><span class="n">setReadCallback</span><span class="p">(</span>
            <span class="n">boost</span><span class="o">::</span><span class="n">bind</span><span class="p">(</span><span class="o">&amp;</span><span class="n">Acceptor</span><span class="o">::</span><span class="n">handleRead</span><span class="p">,</span> <span class="n">this</span><span class="p">));</span>
        <span class="p">...</span> 
    <span class="p">}</span>

    <span class="kt">void</span> <span class="n">Acceptor</span><span class="o">::</span><span class="n">handleRead</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="p">...</span>
        <span class="kt">int</span> <span class="n">connfd</span> <span class="o">=</span> <span class="n">acceptSocket_</span><span class="p">.</span><span class="n">accept</span><span class="p">(</span><span class="o">&amp;</span><span class="n">peerAddr</span><span class="p">);</span> <span class="c1">// 接收连接获得一个新的socket</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">connfd</span> <span class="o">&gt;=</span> <span class="mi">0</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="p">...</span>
            <span class="n">newConnectionCallback_</span><span class="p">(</span><span class="n">connfd</span><span class="p">,</span> <span class="n">peerAddr</span><span class="p">);</span> <span class="c1">// 回调到TcpServer::newConnection</span>
</code></pre>
</div>


<p><code>TcpServer::newConnection</code>中建立一个TcpConnection，并将其附加到一个EventLoopThread中，简单来说就是给其配置一个线程：</p>

<div class="highlight">
<pre><code class="c">    <span class="kt">void</span> <span class="n">TcpServer</span><span class="o">::</span><span class="n">newConnection</span><span class="p">(</span><span class="kt">int</span> <span class="n">sockfd</span><span class="p">,</span> <span class="k">const</span> <span class="n">InetAddress</span><span class="o">&amp;</span> <span class="n">peerAddr</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="p">...</span>
        <span class="n">EventLoop</span><span class="o">*</span> <span class="n">ioLoop</span> <span class="o">=</span> <span class="n">threadPool_</span><span class="o">-&gt;</span><span class="n">getNextLoop</span><span class="p">();</span>
        <span class="n">TcpConnectionPtr</span> <span class="nf">conn</span><span class="p">(</span><span class="n">new</span> <span class="n">TcpConnection</span><span class="p">(</span><span class="n">ioLoop</span><span class="p">,</span>
                                                <span class="n">connName</span><span class="p">,</span>
                                                <span class="n">sockfd</span><span class="p">,</span>
                                                <span class="n">localAddr</span><span class="p">,</span>
                                                <span class="n">peerAddr</span><span class="p">));</span>
        <span class="n">connections_</span><span class="p">[</span><span class="n">connName</span><span class="p">]</span> <span class="o">=</span> <span class="n">conn</span><span class="p">;</span>
        <span class="p">...</span>
        <span class="n">ioLoop</span><span class="o">-&gt;</span><span class="n">runInLoop</span><span class="p">(</span><span class="n">boost</span><span class="o">::</span><span class="n">bind</span><span class="p">(</span><span class="o">&amp;</span><span class="n">TcpConnection</span><span class="o">::</span><span class="n">connectEstablished</span><span class="p">,</span> <span class="n">conn</span><span class="p">));</span>
</code></pre>
</div>


<h2>IO的驱动</h2>

<p>之前提到，一旦要关心某IO事件了，就调用<code>Channel::enableXXX</code>，这个如何实现的呢？</p>

<div class="highlight">
<pre><code class="c">    <span class="n">class</span> <span class="n">Channel</span> <span class="p">{</span>
        <span class="p">...</span>
        <span class="kt">void</span> <span class="n">enableReading</span><span class="p">()</span> <span class="p">{</span> <span class="n">events_</span> <span class="o">|=</span> <span class="n">kReadEvent</span><span class="p">;</span> <span class="n">update</span><span class="p">();</span> <span class="p">}</span>
        <span class="kt">void</span> <span class="n">enableWriting</span><span class="p">()</span> <span class="p">{</span> <span class="n">events_</span> <span class="o">|=</span> <span class="n">kWriteEvent</span><span class="p">;</span> <span class="n">update</span><span class="p">();</span> <span class="p">}</span>
       
    <span class="kt">void</span> <span class="n">Channel</span><span class="o">::</span><span class="n">update</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="n">loop_</span><span class="o">-&gt;</span><span class="n">updateChannel</span><span class="p">(</span><span class="n">this</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="kt">void</span> <span class="n">EventLoop</span><span class="o">::</span><span class="n">updateChannel</span><span class="p">(</span><span class="n">Channel</span><span class="o">*</span> <span class="n">channel</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="p">...</span>
        <span class="n">poller_</span><span class="o">-&gt;</span><span class="n">updateChannel</span><span class="p">(</span><span class="n">channel</span><span class="p">);</span>
    <span class="p">}</span>
</code></pre>
</div>


<p>最终调用到<code>Poller::upateChannel</code>。muduo中有两个Poller的实现，分别是Poll和EPoll，可以选择简单的Poll来看：</p>

<div class="highlight">
<pre><code class="c">    <span class="kt">void</span> <span class="n">PollPoller</span><span class="o">::</span><span class="n">updateChannel</span><span class="p">(</span><span class="n">Channel</span><span class="o">*</span> <span class="n">channel</span><span class="p">)</span>
    <span class="p">{</span>
      <span class="p">...</span>
      <span class="k">if</span> <span class="p">(</span><span class="n">channel</span><span class="o">-&gt;</span><span class="n">index</span><span class="p">()</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span>
      <span class="p">{</span>
        <span class="c1">// a new one, add to pollfds_</span>
        <span class="n">assert</span><span class="p">(</span><span class="n">channels_</span><span class="p">.</span><span class="n">find</span><span class="p">(</span><span class="n">channel</span><span class="o">-&gt;</span><span class="n">fd</span><span class="p">())</span> <span class="o">==</span> <span class="n">channels_</span><span class="p">.</span><span class="n">end</span><span class="p">());</span>
        <span class="k">struct</span> <span class="n">pollfd</span> <span class="n">pfd</span><span class="p">;</span>
        <span class="n">pfd</span><span class="p">.</span><span class="n">fd</span> <span class="o">=</span> <span class="n">channel</span><span class="o">-&gt;</span><span class="n">fd</span><span class="p">();</span>
        <span class="n">pfd</span><span class="p">.</span><span class="n">events</span> <span class="o">=</span> <span class="n">static_cast</span><span class="o">&lt;</span><span class="kt">short</span><span class="o">&gt;</span><span class="p">(</span><span class="n">channel</span><span class="o">-&gt;</span><span class="n">events</span><span class="p">());</span> <span class="c1">// 也就是Channel::enableXXX操作的那个events_</span>
        <span class="n">pfd</span><span class="p">.</span><span class="n">revents</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
        <span class="n">pollfds_</span><span class="p">.</span><span class="n">push_back</span><span class="p">(</span><span class="n">pfd</span><span class="p">);</span> <span class="c1">// 加入一个新的pollfd</span>
        <span class="kt">int</span> <span class="n">idx</span> <span class="o">=</span> <span class="n">static_cast</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span><span class="p">(</span><span class="n">pollfds_</span><span class="p">.</span><span class="n">size</span><span class="p">())</span><span class="o">-</span><span class="mi">1</span><span class="p">;</span>
        <span class="n">channel</span><span class="o">-&gt;</span><span class="n">set_index</span><span class="p">(</span><span class="n">idx</span><span class="p">);</span>
        <span class="n">channels_</span><span class="p">[</span><span class="n">pfd</span><span class="p">.</span><span class="n">fd</span><span class="p">]</span> <span class="o">=</span> <span class="n">channel</span><span class="p">;</span>
</code></pre>
</div>


<p>可见Poller就是把Channel关心的IO事件转换为OS提供的IO模型数据结构上。通过查看关键的<code>pollfds_</code>的使用，可以发现其主要是在Poller::poll接口里。这个接口会在EventLoop的主循环中不断调用：</p>

<div class="highlight">
<pre><code class="c">    <span class="kt">void</span> <span class="n">EventLoop</span><span class="o">::</span><span class="n">loop</span><span class="p">()</span>
    <span class="p">{</span>
      <span class="p">...</span>
      <span class="k">while</span> <span class="p">(</span><span class="o">!</span><span class="n">quit_</span><span class="p">)</span>
      <span class="p">{</span>
        <span class="n">activeChannels_</span><span class="p">.</span><span class="n">clear</span><span class="p">();</span>
        <span class="n">pollReturnTime_</span> <span class="o">=</span> <span class="n">poller_</span><span class="o">-&gt;</span><span class="n">poll</span><span class="p">(</span><span class="n">kPollTimeMs</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">activeChannels_</span><span class="p">);</span>
        <span class="p">...</span>
        <span class="k">for</span> <span class="p">(</span><span class="n">ChannelList</span><span class="o">::</span><span class="n">iterator</span> <span class="n">it</span> <span class="o">=</span> <span class="n">activeChannels_</span><span class="p">.</span><span class="n">begin</span><span class="p">();</span>
            <span class="n">it</span> <span class="o">!=</span> <span class="n">activeChannels_</span><span class="p">.</span><span class="n">end</span><span class="p">();</span> <span class="o">++</span><span class="n">it</span><span class="p">)</span>
        <span class="p">{</span>
          <span class="n">currentActiveChannel_</span> <span class="o">=</span> <span class="o">*</span><span class="n">it</span><span class="p">;</span>
          <span class="n">currentActiveChannel_</span><span class="o">-&gt;</span><span class="n">handleEvent</span><span class="p">(</span><span class="n">pollReturnTime_</span><span class="p">);</span> <span class="c1">// 获得IO事件，通知各注册回调</span>
        <span class="p">}</span>
</code></pre>
</div>


<p>整个流程可总结为：各Channel内部会把自己关心的事件告诉给Poller，Poller由EventLoop驱动检测IO，然后返回哪些Channel发生了事件，EventLoop再驱动这些Channel调用各注册回调。</p>

<p>从这个过程中可以看出，EventLoop就是一个事件产生器。</p>

<h2>线程模型</h2>

<p>在muduo的服务器中，muduo的线程模型是怎样的呢？它如何通过线程来支撑高并发呢？其实很简单，它为每一个线程配置了一个EventLoop，这个线程同时被附加了若干个网络连接，这个EventLoop服务于这些网络连接，为这些连接收集并派发IO事件。</p>

<p>回到<code>TcpServer::newConnection</code>中：</p>

<div class="highlight">
<pre><code class="c">    <span class="kt">void</span> <span class="n">TcpServer</span><span class="o">::</span><span class="n">newConnection</span><span class="p">(</span><span class="kt">int</span> <span class="n">sockfd</span><span class="p">,</span> <span class="k">const</span> <span class="n">InetAddress</span><span class="o">&amp;</span> <span class="n">peerAddr</span><span class="p">)</span>
    <span class="p">{</span>
      <span class="p">...</span>
      <span class="n">EventLoop</span><span class="o">*</span> <span class="n">ioLoop</span> <span class="o">=</span> <span class="n">threadPool_</span><span class="o">-&gt;</span><span class="n">getNextLoop</span><span class="p">();</span>
      <span class="p">...</span>
      <span class="n">TcpConnectionPtr</span> <span class="n">conn</span><span class="p">(</span><span class="n">new</span> <span class="n">TcpConnection</span><span class="p">(</span><span class="n">ioLoop</span><span class="p">,</span> <span class="c1">// 使用这个选择到的线程中的EventLoop</span>
                                              <span class="n">connName</span><span class="p">,</span>
                                              <span class="n">sockfd</span><span class="p">,</span>
                                              <span class="n">localAddr</span><span class="p">,</span>
                                              <span class="n">peerAddr</span><span class="p">));</span>
      <span class="p">...</span>
      <span class="n">ioLoop</span><span class="o">-&gt;</span><span class="n">runInLoop</span><span class="p">(</span><span class="n">boost</span><span class="o">::</span><span class="n">bind</span><span class="p">(</span><span class="o">&amp;</span><span class="n">TcpConnection</span><span class="o">::</span><span class="n">connectEstablished</span><span class="p">,</span> <span class="n">conn</span><span class="p">));</span>
</code></pre>
</div>


<p>注意<code>TcpConnection::connectEstablished</code>是如何通过Channel注册关心的IO事件到<code>ioLoop</code>的。</p>

<p>极端来说，muduo的每一个连接线程可以只为一个网络连接服务，这就有点类似于thread per connection模型了。</p>

<h2>网络模型</h2>

<p>传说中的Reactor模式，以及one loop per thread，基于EventLoop的作用，以及线程池与TcpConnection的关系，可以醍醐灌顶般理解以下这张muduo的网络模型图了：</p>

<p><img src="http://codemacro.com/assets/res/muduo-model.png" width="634" height="480" alt="" /><br /></p>

<h2>总结</h2>

<p>本文主要对muduo的主要结构及主要机制的实现做了描述，其他如Buffer的实现、定时器的实现大家都可以自行研究。muduo的源码很清晰，通过源码及配合<a href="http://blog.csdn.net/solstice">陈硕博客</a>上的内容可以学到一些网络编程方面的经验。</p>

<p class="post-footer">
            原文地址：
            <a href="http://codemacro.com/2014/05/04/muduo-source/">http://codemacro.com/2014/05/04/muduo-source/</a><br />
            written by <a href="http://codemacro.com">Kevin Lynx</a>
            &nbsp;posted at <a href="http://codemacro.com">http://codemacro.com</a>
            </p>

</div><img src ="http://www.cppblog.com/kevinlynx/aggbug/206817.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/kevinlynx/" target="_blank">Kevin Lynx</a> 2014-05-04 18:22 <a href="http://www.cppblog.com/kevinlynx/archive/2014/05/04/206817.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>记一次堆栈平衡错误</title><link>http://www.cppblog.com/kevinlynx/archive/2013/08/15/202568.html</link><dc:creator>Kevin Lynx</dc:creator><author>Kevin Lynx</author><pubDate>Thu, 15 Aug 2013 15:01:00 GMT</pubDate><guid>http://www.cppblog.com/kevinlynx/archive/2013/08/15/202568.html</guid><wfw:comment>http://www.cppblog.com/kevinlynx/comments/202568.html</wfw:comment><comments>http://www.cppblog.com/kevinlynx/archive/2013/08/15/202568.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cppblog.com/kevinlynx/comments/commentRss/202568.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/kevinlynx/services/trackbacks/202568.html</trackback:ping><description><![CDATA[<div class="entry-content">
<p>最近在一个使用Visual Studio开发的C++程序中，出现了如下错误：</p>

<blockquote><p>Run-Time Check Failure #0 - The value of ESP was not properly saved across a function call.  This is usually a result of calling a function declared with one calling convention with a function pointer declared with a different calling convention.</p></blockquote>

<p>这个错误主要指的就是函数调用堆栈不平衡。在C/C++程序中，调用一个函数前会保存当前堆栈信息，目标函数返回后会把堆栈恢复到调用前的状态。函数的参数、局部变量会影响堆栈。而函数堆栈不平衡，一般是因为函数调用方式和目标函数定义方式不一致导致，例如：</p>

<div class="highlight">
<pre><code class="c"><span class="kt">void</span> <span class="kr">__stdcall</span> <span class="nf">func</span><span class="p">(</span><span class="kt">int</span> <span class="n">a</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="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">typedef</span> <span class="kt">void</span> <span class="p">(</span><span class="o">*</span><span class="n">funcptr</span><span class="p">)(</span><span class="kt">int</span><span class="p">);</span>
    <span class="n">funcptr</span> <span class="n">ptr</span> <span class="o">=</span> <span class="p">(</span><span class="n">funcptr</span><span class="p">)</span> <span class="n">func</span><span class="p">;</span>
    <span class="n">ptr</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span> <span class="c1">// 返回后导致堆栈不平衡</span>
    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre>
</div>


<p><code>__stdcall</code>修饰的函数，其函数参数的出栈由被调用者自己完成，而<code>__cdecl</code>，也就是C/C++函数的默认调用约定，则是调用者完成参数出栈。</p>

<!-- more -->


<p>Visual Studio在debug模式下会在我们的代码中加入不少检查代码，例如以上代码对应的汇编中，就会增加一个检查堆栈是否平衡的函数调用，当出现问题时，就会出现提示<code>Run-Time Check Failure...</code>这样的错误对话框：</p>

<pre><code>call dword ptr [ptr]  ; ptr(1)
add  esp,4  ; cdecl方式，调用者清除参数
cmp  esi,esp  
call @ILT+1345(__RTC_CheckEsp) (0B01546h) ; 检查堆栈是否平衡
</code></pre>

<p>但是我们的程序不是这种低级错误。我们调用的函数是放在dll中的，调用约定显示定义为<code>__stdcall</code>，函数声明和实现一致。大致的结构如下：</p>

<div class="highlight">
<pre><code class="c"><span class="n">IParser</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">parser</span><span class="o">-&gt;</span><span class="n">Begin</span><span class="p">();</span>
<span class="p">...</span>
<span class="p">...</span>
<span class="n">parser</span><span class="o">-&gt;</span><span class="n">End</span><span class="p">();</span>
<span class="n">parser</span><span class="o">-&gt;</span><span class="n">Release</span><span class="p">();</span> <span class="c1">// 返回后导致堆栈不平衡</span>
</code></pre>
</div>


<p>IParser的实现在一个dll里，这反而是一个误导人的信息。<code>parser-&gt;Release</code>返回后，堆栈不平衡，<strong>并且仅仅少了一个字节</strong>。一个字节怎么来的？</p>

<p>解决这个问题主要的手段就是跟反汇编，在关键位置查看寄存器和堆栈的内容。编译器生成的代码是正确的，而我们自己的代码乍看上去也没问题。最后甚至使用最傻逼的调试手段&#8211;逐行语句注释查错。</p>

<p>具体查错过程就不细说了。解决问题往往需要更多的冷静，和清晰的思路。最终我使用的方法是，在进入<code>Release</code>之前记录堆栈指针的值，堆栈指针的值会被压入堆栈，以在函数返回后从堆栈弹出，恢复堆栈指针。<code>Release</code>的实现很简单，就是删除一个<code>Parser</code>这个对象，但这个对象的析构会导致很多其他对象被析构。我就逐层地检查，是在哪个函数里改变了堆栈里的内容。</p>

<p>理论上，函数本身是操作不到调用者的堆栈的。而现在看来，确实是被调用函数，也就是<code>Release</code>改写了调用者的堆栈内容。要改变堆栈的内容，只有通过局部变量的地址才能做到。</p>

<p>最终，我发现在调用完以下函数后，我跟踪的堆栈地址内容发生了改变：</p>

<pre><code>call llvm::RefCountedBase&lt;clang::TargetOptions&gt;::Release (10331117h)
</code></pre>

<p>因为注意到<code>TargetOptions</code>这个字眼，想起了在<code>parser-&gt;Begin</code>里有涉及到这个类的使用，类似于：</p>

<div class="highlight">
<pre><code class="c"><span class="n">TargetOptions</span> <span class="n">TO</span><span class="p">;</span>
<span class="p">...</span>
<span class="n">TargetInfo</span> <span class="o">*</span><span class="n">TI</span> <span class="o">=</span> <span class="n">TargetInfo</span><span class="o">::</span><span class="n">CreateTargetInfo</span><span class="p">(</span><span class="n">m_inst</span><span class="p">.</span><span class="n">getDiagnostics</span><span class="p">(),</span> <span class="n">TO</span><span class="p">);</span>
</code></pre>
</div>


<p>这部分初始化代码，是直接从网上复制的，因为并不影响主要逻辑，所以从来没对这块代码深究。查看<code>CreateTargetInfo</code>的源码，<strong>发现这个函数将<code>TO</code>这个局部变量的地址保存了下来</strong>。</p>

<p>而在<code>Release</code>中，则会对这个保存的临时变量进行删除操作，形如：</p>

<div class="highlight">
<pre><code class="c"><span class="kt">void</span> <span class="nf">Delete</span><span class="p">()</span> <span class="k">const</span> <span class="p">{</span>
  <span class="n">assert</span> <span class="p">(</span><span class="n">ref_cnt</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="o">&amp;&amp;</span> <span class="s">"Reference count is already zero."</span><span class="p">);</span>
  <span class="k">if</span> <span class="p">(</span><span class="o">--</span><span class="n">ref_cnt</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="n">delete</span> <span class="n">static_cast</span><span class="o">&lt;</span><span class="k">const</span> <span class="n">Derived</span><span class="o">*&gt;</span><span class="p">(</span><span class="n">this</span><span class="p">);</span>
<span class="p">}</span>
</code></pre>
</div>


<p>但是，<strong>问题并不在于对一个局部变量地址进行delete</strong>，<code>delete</code>在调试模式下是做了内存检测的，那会导致一种断言。</p>

<p><code>TargetOptions</code>包含了<code>ref_cnt</code>这个成员。当出了<code>Begin</code>作用域后，parser保存的<code>TargetOptions</code>的地址，指向的内容（堆栈）发生了改变，也就是<code>ref_cnt</code>这个成员变量的值不再正常。由于一些巧合，主要是代码中各个局部变量、函数调用顺序、函数参数个数（曾尝试去除<code>Begin</code>的参数，可以避免错误提示），导致在调用<code>Release</code>前堆栈指针恰好等于之前保存的<code>TargetOptions</code>的地址。注意，之前保存的<code>TargetOptions</code>的地址，和调用<code>Release</code>前的堆栈指针值相同了。</p>

<p>而在<code>TargetOptions</code>的<code>Delete</code>函数中，进行了<code>--ref_cnt</code>，这个变量是<code>TargetOptions</code>的第一个成员，它的减1，也就导致了堆栈内容的改变。</p>

<p>至此，整个来龙去脉算是摸清。</p>

<p class="post-footer">
            原文地址：
            <a href="http://codemacro.com/2013/08/15/debug-esp-bug/">http://codemacro.com/2013/08/15/debug-esp-bug/</a><br />
            written by <a href="http://codemacro.com">Kevin Lynx</a>
            &nbsp;posted at <a href="http://codemacro.com">http://codemacro.com</a>
            </p>

</div><img src ="http://www.cppblog.com/kevinlynx/aggbug/202568.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/kevinlynx/" target="_blank">Kevin Lynx</a> 2013-08-15 23:01 <a href="http://www.cppblog.com/kevinlynx/archive/2013/08/15/202568.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>分布式程序开发平台ICE概览</title><link>http://www.cppblog.com/kevinlynx/archive/2013/02/15/197845.html</link><dc:creator>Kevin Lynx</dc:creator><author>Kevin Lynx</author><pubDate>Fri, 15 Feb 2013 07:24:00 GMT</pubDate><guid>http://www.cppblog.com/kevinlynx/archive/2013/02/15/197845.html</guid><wfw:comment>http://www.cppblog.com/kevinlynx/comments/197845.html</wfw:comment><comments>http://www.cppblog.com/kevinlynx/archive/2013/02/15/197845.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/kevinlynx/comments/commentRss/197845.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/kevinlynx/services/trackbacks/197845.html</trackback:ping><description><![CDATA[<div class="entry-content">
<p>本文基于ICE Manual及相关文档就ICE的一些主要特性做一个概览，它不是一个tutorial，不是一个guid，更不是manual。</p>

<h2>概览</h2>

<p><a href="http://www.zeroc.com/index.html">ICE</a>，Internet Communications Engine，按照官方介绍，是一个支持C++、.Net、Java、Python、Objective-C、Ruby、PHP及ActionScript等语言的分布式程序开发平台。按照我的理解，简单来说它是一个核心功能包装RPC的库。要把这个RPC包装得漂亮，自然而然，对于使用者而言，调用一个远端的接口和调用一个本地的接口没有什么区别，例如：</p>

<div class="highlight">
<pre><code class="c">    <span class="n">Object</span> <span class="o">*</span><span class="n">obj</span> <span class="o">=</span> <span class="n">xxx</span>
    <span class="n">obj</span><span class="o">-&gt;</span><span class="n">sayHello</span><span class="p">();</span> 
</code></pre>
</div>


<p>ICE包装<code>sayHello</code>接口，当应用层调用该接口时，ICE发送调用请求到远端服务器，接收返回结果并返回给应用层。ICE在接口提供方面，做到了这一点。</p>

<p>以下，我将逐个给出ICE中的一些工具、组件、特性说明，以展示它作为一个分布式程序开发平台所拥有的能力。到目前为止，所有这些信息均来自于ICE相关文档，总结出来权当为自己服务。</p>

<!-- more -->


<h2>Slice</h2>

<p>Slice(Specification Language for Ice)是ICE定义的一种中间语言，其语法类似于C++。对于一个RPC过程而言，例如上面调用远端的<code>sayHello</code>接口，其主要涉及到调用这个接口的参数和返回值传递，当然接口本身的传递不在话下，ICE为了包装这个过程，其使用了这样一种方式：使用者使用Slice语言描述RPC过程中调用的接口，例如该接口属于哪个类，该接口有哪些参数哪些返回值；然后使用者使用ICE提供的Slice编译器（实际上是一个语言翻译程序）将Slice源码翻译成目标语言。而这个目标语言，则是使用者开发应用程序的开发语言，即上文提到的C++、.Net、Java等。</p>

<p>这些翻译出来的目标代码，就封装了<code>sayHello</code>底层实现的一切细节。当然事情没有这么简单，但我们目前只需关注简单的这一部分。ICE之所以支持那么多种开发语言，正是Slice立下的功劳。Slice语言本身的语言特性，实际上受限于目标语言的语言特性，例如Slice支持异常，恰是因为Slice转换的所有语言都包含异常这个语法特性。</p>

<p>Slice还有一个重要特性，在于一份Slice源码被翻译出来的目标代码，一般情况是被服务器和客户端同时使用。</p>

<h2>开发步骤</h2>

<p>使用ICE开发应用程序，其步骤遵循：</p>

<ol>
<li>编写Slice，说明整个RPC中涉及到的接口调用，编译它</li>
<li>基于Slice目标代码和ICE库编写Server</li>
<li>基于Slice目标带啊和ICE库编写Client</li>
</ol>
<h2>一个例子</h2>

<p>有必要展示一个例子，以获得使用ICE开发应用程序的感性认识。这个例子是一个简单的hello world程序，客户端让服务器打印一个字符串。</p>

<ul>
<li>编写Slice</li>
</ul>
<div class="highlight">
<pre><code class="c">    <span class="c1">// Printer.ice，Slice源码后缀为ice</span>
    <span class="n">module</span> <span class="n">Demo</span> <span class="p">{</span>
        <span class="n">interface</span> <span class="n">Printer</span> <span class="p">{</span>
            <span class="kt">void</span> <span class="n">printString</span><span class="p">(</span><span class="n">string</span> <span class="n">s</span><span class="p">);</span>
        <span class="p">};</span>
    <span class="p">};</span>
</code></pre>
</div>


<p>使用ICE提供的程序翻译为C++代码：</p>

<div class="highlight">
<pre><code class="c">    <span class="err">$</span> <span class="n">slice2cpp</span> <span class="n">Printer</span><span class="p">.</span><span class="n">ice</span>
</code></pre>
</div>


<p>得到Printer.cpp和Printer.h。Slice翻译出来的目标代码除了封装RPC交互的一些细节外，最重要的，因为本身Slice文件其实是定义接口，但接口的实现，则需要应用层来做。</p>

<ul>
<li>服务器端使用生成的Printer.cpp/.h，并实现Printer接口</li>
</ul>
<div class="highlight">
<pre><code class="c">    <span class="c1">// 翻译出来的Printer.h中有对应于Slice中定义的Printer类，及需要实现的printString接口</span>
    <span class="n">class</span> <span class="n">PrinterI</span> <span class="o">:</span> <span class="n">public</span> <span class="n">Printer</span> <span class="p">{</span>
    <span class="nl">public:</span>
        <span class="n">virtual</span> <span class="kt">void</span> <span class="n">printString</span><span class="p">(</span><span class="k">const</span> <span class="n">string</span><span class="o">&amp;</span> <span class="n">s</span><span class="p">,</span> <span class="k">const</span> <span class="n">Ice</span><span class="o">::</span><span class="n">Current</span><span class="o">&amp;</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">count</span> <span class="o">&lt;&lt;</span> <span class="n">s</span> <span class="o">&lt;&lt;</span> <span class="n">endl</span><span class="p">;</span>
        <span class="p">}</span>
    <span class="p">};</span>
</code></pre>
</div>


<ul>
<li>客户端使用生成的Printer.cpp/.h，通过ICE获得一个<code>Printer</code>对象，然后调用其<code>printString</code>接口</li>
</ul>
<div class="highlight">
<pre><code class="c">    <span class="c1">// don't care about this</span>
    <span class="n">PrinterPrx</span> <span class="n">printer</span> <span class="o">=</span> <span class="n">PrinterPrx</span><span class="o">::</span><span class="n">checkedCast</span><span class="p">(</span><span class="n">base</span><span class="p">);</span>
    <span class="n">printer</span><span class="o">-&gt;</span><span class="n">printString</span><span class="p">(</span><span class="s">"Hello World!"</span><span class="p">);</span>
</code></pre>
</div>


<p>使用ICE开发应用程序，整体过程即为以上展示。</p>

<h2>概念</h2>

<p>ICE包含了很多概念，作为一个开发平台而言，有其专有术语一点不过分，熟悉这些概念可以更容易学习ICE。这里罗列一些关键概念。</p>

<h3>服务器端和客户端</h3>

<p>ICE中的服务器端和客户端和一般网络程序中的概念不太一样。在若干个交互的网络程序中，我们都很好理解这样一种现象：某个程序有多个角色，它可能是作为A程序的服务器端，也可能是作为B程序的客户端。ICE中的服务器和客户端角色更容易变换。</p>

<p>以Printer例子为例，如果我们的<code>printString</code>接口有一个回调函数参数（这在ICE中很常见），服务器实现<code>printString</code>时，当其打印出字符串后，需通过该回调函数通知客户端。这样的回调机制在ICE的实现中，会创建一个新的网络连接，而此时，这个原有的服务器端就变成了原有客户端的客户。当然，你也可以避免这样的情况出现。</p>

<h3>ICE Objects/Object Adapter/Facet</h3>

<p>对于<code>Printer</code>例子，一个<code>Printer</code>对象可以被看作是一个ICE Objects。Object可以说是服务器端提供给客户端的接口。所以在服务器端通常会创建出很多个Object。服务器端使用Object Adapter对象去保存这些Object。例如，一个典型的ICE对象在初始化时可能包含以下代码：</p>

<div class="highlight">
<pre><code class="c">    <span class="c1">// 创建一个Object Adapter</span>
    <span class="n">Ice</span><span class="o">::</span><span class="n">ObjectAdapterPtr</span> <span class="n">adapter</span> <span class="o">=</span> <span class="n">communicator</span><span class="p">()</span><span class="o">-&gt;</span><span class="n">createObjectAdapter</span><span class="p">(</span><span class="s">"Hello"</span><span class="p">);</span>
    <span class="c1">// 创建一个Object，形如Printer</span>
    <span class="n">Demo</span><span class="o">::</span><span class="n">HelloPtr</span> <span class="n">hello</span> <span class="o">=</span> <span class="n">new</span> <span class="n">HelloI</span><span class="p">;</span>
    <span class="c1">// 将Object加入到Object Adapter</span>
    <span class="n">adapter</span><span class="o">-&gt;</span><span class="n">add</span><span class="p">(</span><span class="n">hello</span><span class="p">,</span> <span class="n">communicator</span><span class="p">()</span><span class="o">-&gt;</span><span class="n">stringToIdentity</span><span class="p">(</span><span class="s">"hello"</span><span class="p">));</span>
</code></pre>
</div>


<p>Facet是Object的一部分，或者说Object是Facet的一个集合，摘Ice manual中的一句话：</p>

<blockquote><p>An Ice object is actually a collection of sub-objects known as facets whose types are not necessarily related.</p></blockquote>

<h3>Proxy</h3>

<p>Proxy是ICE客户端里的概念。客户端通过Proxy访问服务器端上的Object，通过Proxy调用服务器端Object上提供的接口。在客户端上一般有类似以下代码：</p>

<div class="highlight">
<pre><code class="c">    <span class="n">Ice</span><span class="o">::</span><span class="n">ObjectPrx</span> <span class="n">base</span> <span class="o">=</span> <span class="n">ic</span><span class="o">-&gt;</span><span class="n">stringToProxy</span><span class="p">(</span><span class="s">"SimplePrinter:default -p 10000"</span><span class="p">);</span>
    <span class="c1">// Printer Proxy</span>
    <span class="n">PrinterPrx</span> <span class="n">printer</span> <span class="o">=</span> <span class="n">PrinterPrx</span><span class="o">::</span><span class="n">checkedCast</span><span class="p">(</span><span class="n">base</span><span class="p">);</span>
    <span class="n">printer</span><span class="o">-&gt;</span><span class="n">printString</span><span class="p">(</span><span class="s">"Hello World!"</span><span class="p">);</span>
</code></pre>
</div>


<p>Proxy又分为几种，包括：</p>

<h4>Direct Proxy</h4>

<p>Direct Proxy，这里的<code>direct</code>意指这个proxy访问的object时，是否携带了地址(EndPoint)信息，例如上面例子中<code>SimplePrinter:default -p 10000</code>就是一个地址。</p>

<h4>Indirect Proxy</h4>

<p>Indirect Proxy相对Direct Proxy而言，其没有具体的地址，仅仅是一个符号。通常包含两种形式：</p>

<ul>
<li>SimplePrinter</li>
<li>SimplePrinter@PrinterAdapter</li>
</ul>
<p>为了获取真正的地址，客户端需要一个定位服务（location service）来获取这个符号对应的地址。ICE中提供了一些默认的服务程序，IceGrid就是其中之一，而IceGrid的作用就包括定位具体的地址，即翻译符号地址到具体的地址。</p>

<p>这里Indirect Proxy可以看作一个域名，而Direct Proxy可以看作是IP地址。Indirect Proxy使用时，就需要借助DNS翻译得到域名对应的IP地址。</p>

<h4>Fixed Proxy</h4>

<p>由于Proxy是用于与服务器端的Object通信的，客户端借助Proxy来访问服务器端的Object，所以Proxy通常都会对应一个真实的网络连接。在ICE中，一般的Proxy于网络连接(Connection)实际上是没有太大关联的。一个Proxy可以没有Connection，也可以在建立这个Connection后又断开之。但是，ICE提供了一种特殊的Proxy，Fixed Proxy，这种Proxy紧密地与一个Connection绑定在一起，其生命周期被强制关联起来。</p>

<p>关于Fixed Proxy可以参看ICE Manual <a href="http://doc.zeroc.com/display/Doc/Connection+Management+in+Ice">Connection Management</a>。</p>

<h3>其他</h3>

<ul>
<li>AMI</li>
</ul>
<p>Asynchronous Method Invocation，对于客户端而言，用于表示某个服务器端接口是异步操作，需在Slice中使用metadata来修饰这个接口，例如：</p>

<div class="highlight">
<pre><code class="c">    <span class="p">[</span><span class="s">"ami"</span><span class="p">]</span>  <span class="kt">void</span> <span class="n">sayHello</span><span class="p">(</span><span class="kt">int</span> <span class="n">delay</span><span class="p">)</span>
</code></pre>
</div>


<ul>
<li>AMD</li>
</ul>
<p>Asynchronous method dispatch，这个针对于服务器端，同样表示这个接口是异步操作，需在Slice中使用metadata来修饰这个接口：</p>

<div class="highlight">
<pre><code class="c">    <span class="p">[</span><span class="s">"ami"</span><span class="p">,</span> <span class="s">"amd"</span><span class="p">]</span>  <span class="kt">void</span> <span class="n">sayHello</span><span class="p">(</span><span class="kt">int</span> <span class="n">delay</span><span class="p">)</span>
</code></pre>
</div>


<p>通常对于这种异步接口而言，都需要使用Slice metadata <code>ami</code>和<code>amd</code>同时修饰。</p>

<ul>
<li>idempotent</li>
</ul>
<p>idempotent是Slice中的概念，同const一样用于修饰某个接口的特性。idempotent表示该接口无论调用多少次，其执行结果都是相同的，例如一般的<code>get</code>类接口。</p>

<ul>
<li>batched invocation</li>
</ul>
<p>客户端调用服务器端的接口这个动作称为<code>invocation</code>。就像网络层的数据缓存一样，ICE对于接口调用也可能暂时缓存，当多个提交请求缓存起来后，然后调用刷新接口全部刷新到服务器端，则称为<code>batched invocation</code>。</p>

<h2>服务</h2>

<p>ICE除了提供一个库之外，还提供了一些应用程序。这些应用程序本身也是一些服务器，提供了一些方便的功能方便我们开发分布式程序。</p>

<h3>Freeze</h3>

<p>Freeze用于将Slice对象持久化到数据库中，按照Manual里的说法，它应该是一个编译器，可以生成一些持久化操作的代码。Freeze持久化对象时使用的数据库是Berkeley DB。</p>

<blockquote><p>Ice has a built-in object persistence service, known as Freeze. Freeze makes it easy to store object state in a database: you define the state stored by your objects in Slice, and the Freeze compiler generates code that stores and retrieves object state to and from a database. Freeze uses Berkeley DB as its database.</p></blockquote>

<p>FreezeScript有点类似于Rails中的数据库操作工具，可用于操作持久化到数据库中的对象数据。</p>

<blockquote><p>Ice also offers a tool set collectively called FreezeScript that makes it easier to maintain databases and to migrate the contents of existing databases to a new schema if the type definitions of objects change.</p></blockquote>

<h3>IceBox</h3>

<p>IceBox可用于管理服务器中的动态组件。这些动态组件本质上也是提供服务的ICE程序。在形式上，这些组件可以是动态连接库。</p>

<blockquote><p>IceBox is a simple application server that can orchestrate the starting and stopping of a number of application components. Application components can be deployed as a dynamic library instead of as a process.</p></blockquote>

<h3>IceGrid</h3>

<p>IceGrid相当于一个DNS解析服务，可以让服务器不用配置EndPoint，客户端也不用指定服务器的EndPoint，以方便大量的服务器部署。在一般的应用中，我们需要为ICE服务器指定绑定的网络地址（IP和端口），同时也需要为客户端指定服务器端的地址信息。当服务增加到一定数量时，就会存在管理上和配置上的麻烦。而IceGrid则是用于避免这种麻烦，将服务器端和客户端上的地址信息通过一个符号代替，就像我们把Internet上的服务器使用域名来标识一样。</p>

<p>但IceGrid的作用不仅如此，通过配合部署一系列称为IceGrid Node的程序，IceGrid还可以管理各个服务器的启动、关闭、宕机重启等，其中甚至包括负载均衡。</p>

<blockquote><p>IceGrid provides a facility to activate servers on demand, when a client first invokes an operation.
Server activation is taken care of by IceGrid nodes. You must run an IceGrid node on each machine on which you want IceGrid to start servers on demand.</p></blockquote>

<p>简要介绍可以参看ICE Manual <a href="http://doc.zeroc.com/display/Doc/Teach+Yourself+IceGrid+in+10+Minutes">Teach Yourself IceGrid in 10 minutes</a></p>

<h3>Glacier2</h3>

<blockquote><p>Glacier2 is a lightweight firewall traversal solution for Ice applications.</p></blockquote>

<p>按我的理解，Glacier2就像一个网关服务器。它被部署在服务器和客户端之间，我们的服务器群部署在内网，外网不可访问，然后通过Glacier2，外部网络的客户端就可以访问内网的服务器群提供的服务。</p>

<p>对于服务器的开发而言，使用Glacier2，服务器端不需要做任何改动。客户端需要配置Glacier2服务的地址信息，也需要配置要使用服务器的地址信息。Glacier2通过客户端欲访问的服务器地址，在内网定位到真实的服务器，并转发请求提供服务。</p>

<p>Glacier2支持验证客户端，从这一点看来，它又有点像一个验证服务器。通过验证客户端，以提供被正确授权的客户端以完整服务。</p>

<p>Glacier2的工作过程可以描述为：</p>

<blockquote><p>When a client invokes an operation on a routed proxy, the client connects to one of Glacier2’s client endpoints and sends the request as if Glacier2 is the server. Glacier2 then establishes an outgoing connection to the client’s intended server in the private network, forwards the request to that server, and returns the reply (if any) to the client. Glacier2 is essentially acting as a local client on behalf of the remote client.</p></blockquote>

<p>一个Glacier2可服务于若干个客户端和服务器。</p>

<p>详细参看ICE Manual <a href="http://doc.zeroc.com/display/Ice/Glacier2">Glacier2</a></p>

<h2>管理</h2>

<p>ICE服务器可以提供给外部一定的管理功能，包括：关闭服务器、读取服务器配置。这个功能是通过操作Ice.Admin这个Ice Object来实现的。这个Object包含两个Facet：Process和Property，分别对应于关闭服务器和读取服务器配置功能。</p>

<p>对于需要管理服务器的客户端而言，可以大致通过如下代码来完成：</p>

<div class="highlight">
<pre><code class="c">    <span class="c1">// 可以通过communicator来获取这个admin object</span>
    <span class="n">Ice</span><span class="o">::</span><span class="n">ObjectPrx</span> <span class="n">adminObj</span> <span class="o">=</span> <span class="p">...;</span>
    <span class="c1">// 获取admin object里的property facet</span>
    <span class="n">Ice</span><span class="o">::</span><span class="n">PropertiesAdminPrx</span> <span class="n">propAdmin</span> <span class="o">=</span> <span class="n">Ice</span><span class="o">::</span><span class="n">PropertiesAdminPrx</span><span class="o">::</span><span class="n">checkedCast</span><span class="p">(</span><span class="n">adminObj</span><span class="p">,</span> <span class="s">"Properties"</span><span class="p">);</span>
    <span class="n">Ice</span><span class="o">::</span><span class="n">PropertyDict</span> <span class="n">props</span> <span class="o">=</span> <span class="n">propAdmin</span><span class="o">-&gt;</span><span class="n">getPropertiesForPrefix</span><span class="p">(</span><span class="s">""</span><span class="p">);</span>
</code></pre>
</div>


<p>详细参看ICE Manual <a href="http://doc.zeroc.com/display/Ice/Administrative+Facility">Administrative Facility</a></p>

<h2>连接管理</h2>

<p>前已述及，ICE中的网络连接隐藏于Proxy之下。Proxy有两个接口可以获取这个连接对象：</p>

<div class="highlight">
<pre><code class="c">    <span class="n">ice_getConnection</span>
    <span class="n">ice_getCachedConnection</span>
</code></pre>
</div>


<p>例如：</p>

<div class="highlight">
<pre><code class="c">    <span class="n">HelloPrx</span> <span class="n">hello</span> <span class="o">=</span> <span class="n">HelloPrx</span><span class="o">::</span><span class="n">uncheckedCast</span><span class="p">(</span><span class="n">communicator</span><span class="o">-&gt;</span><span class="n">stringToProxy</span><span class="p">(</span><span class="s">"hello:tcp -h remote.host.com -p 10000"</span><span class="p">));</span>
    <span class="n">ConnectionPtr</span> <span class="n">conn</span> <span class="o">=</span> <span class="n">hello</span><span class="o">-&gt;</span><span class="n">ice_getConnection</span><span class="p">();</span>
</code></pre>
</div>


<p>ICE隐藏了网络连接的细节。当ICE发现需要建立连接时才会去建立，例如以上例子中当获得一个Proxy时（这里是HelloPrx），ICE并不建立网络连接，当某个时刻通过该Proxy调用服务器端的某个接口时，ICE发现对应的网络连接没有建立，则发起网络连接。</p>

<p>以上例子在获取Proxy时，使用了<code>uncheckCast</code>，关于<code>checkedCast</code>和<code>uncheckedCast</code>，也影响着网络连接的建立逻辑：</p>

<blockquote><p>On the other hand, if the code were to use a checkedCast instead, then connection establishment would take place as part of the checkedCast, because a checked cast requires a remote call to determine whether the target object supports the specified interface.</p></blockquote>

<p>关于连接管理，ICE使用了一个称为ACM的机制，即Active connection management。当某个连接非active一段时间后，ICE就会主动关闭此连接。应用层当然可以控制这个行为。</p>

<p>详细参看ICE Manual <a href="http://doc.zeroc.com/display/Doc/Connection+Management+in+Ice">Connection Management</a></p>

<p class="post-footer">
            原文地址：
            <a href="http://codemacro.com/2013/02/15/ice-overview/">http://codemacro.com/2013/02/15/ice-overview/</a><br>
            written by <a href="http://codemacro.com">Kevin Lynx</a>
             posted at <a href="http://codemacro.com">http://codemacro.com</a>
            </p>

</div><img src ="http://www.cppblog.com/kevinlynx/aggbug/197845.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/kevinlynx/" target="_blank">Kevin Lynx</a> 2013-02-15 15:24 <a href="http://www.cppblog.com/kevinlynx/archive/2013/02/15/197845.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>使用Clang实现C语言编程规范检查</title><link>http://www.cppblog.com/kevinlynx/archive/2013/02/12/197810.html</link><dc:creator>Kevin Lynx</dc:creator><author>Kevin Lynx</author><pubDate>Tue, 12 Feb 2013 13:53:00 GMT</pubDate><guid>http://www.cppblog.com/kevinlynx/archive/2013/02/12/197810.html</guid><wfw:comment>http://www.cppblog.com/kevinlynx/comments/197810.html</wfw:comment><comments>http://www.cppblog.com/kevinlynx/archive/2013/02/12/197810.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/kevinlynx/comments/commentRss/197810.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/kevinlynx/services/trackbacks/197810.html</trackback:ping><description><![CDATA[<div class="entry-content">
<h2>概述</h2>

<p>Clang是LLVM编译器工具集的前端部分，也就是涵盖词法分析、语法语义分析的部分。而LLVM是Apple在Mac OS上用于替代GCC工具集的编译器软件集合。Clang支持类C语言的语言，例如C、C++、Objective C。Clang的与众不同在于其模块化的设计，使其不仅实现编译器前端部分，并且包装成库的形式提供给上层应用。使用Clang可以做诸如语法高亮、语法检查、编程规范检查方面的工作，当然也可以作为你自己的编译器前端。</p>

<p>编程规范一般包含编码格式和语义规范两部分。编码格式用于约定代码的排版、符号命名等；而语义规范则用于约定诸如类型匹配、表达式复杂度等，例如不允许对常数做逻辑运算、检查变量使用前是否被赋值等。本文描述的主要是基于语义方面的检查，其经验来自于最近做的一个检查工具，该工具实现了超过130条的规范。这份规范部分规则来自于<a href="http://en.wikipedia.org/wiki/MISRA_C">MISRA C</a></p>

<h2>编程模式</h2>

<p>编译器前端部分主要是输出代码对应的抽象语法树(AST)。Clang提供给上层的接口也主要是围绕语法树来做操作。通过google一些Clang的资料，你可能会如我当初一样对该如何正确地使用Clang心存疑惑。我最后使用的方式是基于RecursiveASTVisitor。这是一种类似回调的使用机制，通过提供特定语法树节点的接口，Clang在遍历语法树的时候，在遇到该节点时，就会调用到上层代码。不能说这是最好的方式，但起码它可以工作。基于RecursiveASTVisitor使用Clang，程序主体框架大致为：</p>

<!-- more -->




<div class="highlight">
<pre><code class="c"><span class="c1">// 编写你感兴趣的语法树节点访问接口，例如该例子中提供了函数调用语句和goto语句的节点访问接口</span>
<span class="n">class</span> <span class="n">MyASTVisitor</span> <span class="o">:</span> <span class="n">public</span> <span class="n">RecursiveASTVisitor</span><span class="o">&lt;</span><span class="n">MyASTVisitor</span><span class="o">&gt;</span> <span class="p">{</span>
<span class="nl">public:</span>
    <span class="kt">bool</span> <span class="n">VisitCallExpr</span><span class="p">(</span><span class="n">CallExpr</span> <span class="o">*</span><span class="n">expr</span><span class="p">);</span>

    <span class="kt">bool</span> <span class="nf">VisitGotoStmt</span><span class="p">(</span><span class="n">GotoStmt</span> <span class="o">*</span><span class="n">stmt</span><span class="p">);</span>
    <span class="p">...</span>
<span class="p">};</span>

<span class="n">class</span> <span class="n">MyASTConsumer</span> <span class="o">:</span> <span class="n">public</span> <span class="n">ASTConsumer</span> <span class="p">{</span>
<span class="nl">public:</span>
    <span class="n">virtual</span> <span class="kt">bool</span> <span class="n">HandleTopLevelDecl</span><span class="p">(</span><span class="n">DeclGroupRef</span> <span class="n">DR</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">for</span> <span class="p">(</span><span class="n">DeclGroupRef</span><span class="o">::</span><span class="n">iterator</span> <span class="n">b</span> <span class="o">=</span> <span class="n">DR</span><span class="p">.</span><span class="n">begin</span><span class="p">(),</span> <span class="n">e</span> <span class="o">=</span> <span class="n">DR</span><span class="p">.</span><span class="n">end</span><span class="p">();</span> <span class="n">b</span> <span class="o">!=</span> <span class="n">e</span><span class="p">;</span> <span class="o">++</span><span class="n">b</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">Visitor</span><span class="p">.</span><span class="n">TraverseDecl</span><span class="p">(</span><span class="o">*</span><span class="n">b</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="k">return</span> <span class="nb">true</span><span class="p">;</span>
    <span class="p">}</span> 
    
<span class="nl">private:</span>
    <span class="n">MyASTVisitor</span> <span class="n">Visitor</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">CompilerInstance</span> <span class="n">inst</span><span class="p">;</span>
    <span class="n">Rewriter</span> <span class="n">writer</span><span class="p">;</span>
    <span class="n">inst</span><span class="p">.</span><span class="n">createFileManager</span><span class="p">();</span>
    <span class="n">inst</span><span class="p">.</span><span class="n">createSourceManager</span><span class="p">(</span><span class="n">inst</span><span class="p">.</span><span class="n">getFileManager</span><span class="p">());</span>
    <span class="n">inst</span><span class="p">.</span><span class="n">createPreprocessor</span><span class="p">();</span>
    <span class="n">inst</span><span class="p">.</span><span class="n">createASTContext</span><span class="p">();</span>
    <span class="n">writer</span><span class="p">.</span><span class="n">setSourceMgr</span><span class="p">(</span><span class="n">inst</span><span class="p">.</span><span class="n">getSourceManager</span><span class="p">(),</span> <span class="n">inst</span><span class="p">.</span><span class="n">getLangOpts</span><span class="p">());</span>
    <span class="p">...</span> <span class="c1">// 其他初始化CompilerInstance的代码</span>
  
    <span class="k">const</span> <span class="n">FileEntry</span> <span class="o">*</span><span class="n">fileIn</span> <span class="o">=</span> <span class="n">fileMgr</span><span class="p">.</span><span class="n">getFile</span><span class="p">(</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">]);</span>
    <span class="n">sourceMgr</span><span class="p">.</span><span class="n">createMainFileID</span><span class="p">(</span><span class="n">fileIn</span><span class="p">);</span>
    <span class="n">inst</span><span class="p">.</span><span class="n">getDiagnosticClient</span><span class="p">().</span><span class="n">BeginSourceFile</span><span class="p">(</span><span class="n">inst</span><span class="p">.</span><span class="n">getLangOpts</span><span class="p">(),</span> <span class="o">&amp;</span><span class="n">inst</span><span class="p">.</span><span class="n">getPreprocessor</span><span class="p">());</span>
    <span class="n">MyASTConsumer</span> <span class="n">consumer</span><span class="p">(</span><span class="n">writer</span><span class="p">);</span>
    <span class="n">ParseAST</span><span class="p">(</span><span class="n">inst</span><span class="p">.</span><span class="n">getPreprocessor</span><span class="p">(),</span> <span class="o">&amp;</span><span class="n">consumer</span><span class="p">,</span> <span class="n">inst</span><span class="p">.</span><span class="n">getASTContext</span><span class="p">());</span>
    <span class="n">inst</span><span class="p">.</span><span class="n">getDiagnosticClient</span><span class="p">().</span><span class="n">EndSourceFile</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>以上代码中，ParseAST为Clang开始分析代码的主入口，其中提供了一个ASTConsumer。每次分析到一个顶层定义时(Top level decl)就会回调MyASTConsumer::HandleTopLevelDecl，该函数的实现里调用MyASTVisitor开始递归访问该节点。这里的<code>decl</code>实际上包含定义。</p>

<p>这里使用Clang的方式来源于<a href="http://eli.thegreenplace.net/2012/06/08/basic-source-to-source-transformation-with-clang/">Basic source-to-source transformation with Clang</a>。</p>

<h2>语法树</h2>

<p>Clang中视所有代码单元为语句(statement)，Clang中使用类<code>Stmt</code>来代表statement。Clang构造出来的语法树，其节点类型就是<code>Stmt</code>。针对不同类型的语句，Clang有对应的<code>Stmt</code>子类，例如<code>GotoStmt</code>。Clang中的表达式也被视为语句，Clang使用<code>Expr</code>类来表示表达式，而<code>Expr</code>本身就派生于<code>Stmt</code>。</p>

<p>每个语法树节点都会有一个子节点列表，在Clang中一般可以使用如下语句遍历一个节点的子节点：</p>

<div class="highlight">
<pre><code class="c"><span class="k">for</span> <span class="p">(</span><span class="n">Stmt</span><span class="o">::</span><span class="n">child_iterator</span> <span class="n">it</span> <span class="o">=</span> <span class="n">stmt</span><span class="o">-&gt;</span><span class="n">child_begin</span><span class="p">();</span> <span class="n">it</span> <span class="o">!=</span> <span class="n">stmt</span><span class="o">-&gt;</span><span class="n">child_end</span><span class="p">();</span> <span class="o">++</span><span class="n">it</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">Stmt</span> <span class="o">*</span><span class="n">child</span> <span class="o">=</span> <span class="o">*</span><span class="n">it</span><span class="p">;</span>
<span class="p">}</span>
</code></pre>
</div>


<p>但遗憾的是，无法从一个语法树节点获取其父节点，这将给我们的规范检测工具的实现带来一些麻烦。</p>

<h3>TraverseXXXStmt</h3>

<p>在自己实现的Visitor中（例如MyASTVisitor），除了可以提供VisitXXXStmt系列接口去访问某类型的语法树节点外，还可以提供TraverseXXXStmt系列接口。Traverse系列的接口包装对应的Visit接口，即他们的关系大致为：</p>

<div class="highlight">
<pre><code class="c"><span class="kt">bool</span> <span class="nf">TraverseGotoStmt</span><span class="p">(</span><span class="n">GotoStmt</span> <span class="o">*</span><span class="n">s</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">VisitGotoStmt</span><span class="p">(</span><span class="n">s</span><span class="p">);</span>
    <span class="k">return</span> <span class="nb">true</span><span class="p">;</span>
<span class="p">}</span>
</code></pre>
</div>


<p>例如对于GotoStmt节点而言，Clang会先调用TraverseGotoStmt，在TraverseGotoStmt的实现中才会调用VisitGotoStmt。利用Traverse和Visit之间的调用关系，我们可以解决一些因为不能访问某节点父节点而出现的问题。例如，我们需要限制逗号表达式的使用，在任何地方一旦检测到逗号表达式的出现，都给予警告，除非这个逗号表达式出现在for语句中，例如：</p>

<div class="highlight">
<pre><code class="c"><span class="n">a</span> <span class="o">=</span> <span class="p">(</span><span class="n">a</span> <span class="o">=</span> <span class="mi">1</span><span class="p">,</span> <span class="n">b</span> <span class="o">=</span> <span class="mi">2</span><span class="p">);</span> <span class="cm">/* 违反规范，非法 */</span>
<span class="k">for</span> <span class="p">(</span><span class="n">a</span> <span class="o">=</span> <span class="mi">1</span><span class="p">,</span> <span class="n">b</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span> <span class="n">a</span> <span class="o">&lt;</span> <span class="mi">2</span><span class="p">;</span> <span class="o">++</span><span class="n">a</span><span class="p">)</span> <span class="cm">/* 合法 */</span>
</code></pre>
</div>


<p>逗号表达式对应的访问接口为VisitBinComma，所以我们只需要提供该接口的实现即可：</p>

<div class="highlight">
<pre><code class="c"><span class="n">class</span> <span class="n">MyASTVisitor</span> <span class="o">:</span> <span class="n">public</span> <span class="n">RecursiveASTVisitor</span><span class="o">&lt;</span><span class="n">MyASTVisitor</span><span class="o">&gt;</span> <span class="p">{</span>
<span class="nl">public:</span>
    <span class="p">...</span>
    <span class="kt">bool</span> <span class="n">VisitBinComma</span><span class="p">(</span><span class="n">BinaryOperator</span> <span class="o">*</span><span class="n">stmt</span><span class="p">)</span> <span class="p">{</span>
        <span class="cm">/* 报告错误 */</span>
        <span class="k">return</span> <span class="nb">true</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="p">...</span>
<span class="p">};</span>
</code></pre>
</div>


<p>（注：BinaryOperator用于表示二目运算表达式，例如a + b，逗号表达式也是二目表达式）</p>

<p>但在循环中出现的逗号表达式也会调用到VisitBinComma。为了有效区分该逗号表达式是否出现在for语句中，我们可以期望获取该逗号表达式的父节点，并检查该父节点是否为for语句。但Clang并没有提供这样的能力，我想很大一部分原因在于臆测语法树（抽象语法树）节点的组织结构（父节点、兄弟节点）本身就不是一个确定的事。</p>

<p>这里的解决办法是通过提供TraverseForStmt，以在进入for语句前得到一个标识：</p>

<div class="highlight">
<pre><code class="c"><span class="n">class</span> <span class="n">MyASTVisitor</span> <span class="o">:</span> <span class="n">public</span> <span class="n">RecursiveASTVisitor</span><span class="o">&lt;</span><span class="n">MyASTVisitor</span><span class="o">&gt;</span> <span class="p">{</span>
<span class="nl">public:</span>
    <span class="p">...</span>
    <span class="c1">// 这个函数的实现可以参考RecursiveASTVisitor的默认实现，我们唯一要做的就是在for语句的头那设定一个标志m_inForLine</span>
    <span class="kt">bool</span> <span class="n">TraverseForStmt</span><span class="p">(</span><span class="n">ForStmt</span> <span class="o">*</span><span class="n">s</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">WalkUpFromForStmt</span><span class="p">(</span><span class="n">s</span><span class="p">))</span>
            <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
        <span class="n">m_inForLine</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>
        <span class="k">for</span> <span class="p">(</span><span class="n">Stmt</span><span class="o">::</span><span class="n">child_range</span> <span class="n">range</span> <span class="o">=</span> <span class="n">s</span><span class="o">-&gt;</span><span class="n">children</span><span class="p">();</span> <span class="n">range</span><span class="p">;</span> <span class="o">++</span><span class="n">range</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">range</span> <span class="o">==</span> <span class="n">s</span><span class="o">-&gt;</span><span class="n">getBody</span><span class="p">())</span>
                <span class="n">m_inForLine</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>
            <span class="n">TraverseStmt</span><span class="p">(</span><span class="o">*</span><span class="n">range</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="k">return</span> <span class="nb">true</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="kt">bool</span> <span class="n">VisitBinComma</span><span class="p">(</span><span class="n">BinaryOperator</span> <span class="o">*</span><span class="n">stmt</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">m_inForLine</span><span class="p">)</span> <span class="p">{</span>
            <span class="cm">/* 报告错误 */</span>
        <span class="p">}</span>
        <span class="k">return</span> <span class="nb">true</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="p">...</span>
<span class="p">};</span>
</code></pre>
</div>


<p>（注：严格来说，我们必须检查逗号表达式是出现在for语句的头中，而不包括for语句循环体）</p>

<h2>类型信息</h2>

<p>对于表达式(<code>Expr</code>)而言，都有一个类型信息。Clang直接用于表示类型的类是<code>QualType</code>，实际上这个类只是一个接口包装。这些类型信息可以用于很多类型相关的编程规范检查。例如不允许定义超过2级的指针(例如int ***p)：</p>

<div class="highlight">
<pre><code class="c"><span class="kt">bool</span> <span class="n">MyASTVisitor</span><span class="o">::</span><span class="n">VisitVarDecl</span><span class="p">(</span><span class="n">VarDecl</span> <span class="o">*</span><span class="n">decl</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// 当发现变量定义时该接口被调用</span>
    <span class="n">QualType</span> <span class="n">t</span> <span class="o">=</span> <span class="n">decl</span><span class="o">-&gt;</span><span class="n">getType</span><span class="p">();</span> <span class="c1">// 取得该变量的类型</span>
    <span class="kt">int</span> <span class="n">pdepth</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="c1">// check pointer level</span>
    <span class="k">for</span> <span class="p">(</span> <span class="p">;</span> <span class="n">t</span><span class="o">-&gt;</span><span class="n">isPointerType</span><span class="p">();</span> <span class="n">t</span> <span class="o">=</span> <span class="n">t</span><span class="o">-&gt;</span><span class="n">getPointeeType</span><span class="p">())</span> <span class="p">{</span> <span class="c1">// 如果是指针类型就获取其指向类型(PointeeType)</span>
        <span class="o">++</span><span class="n">pdepth</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">pdepth</span> <span class="o">&gt;=</span> <span class="mi">3</span><span class="p">)</span>
        <span class="cm">/* 报告错误 */</span>
<span class="p">}</span>
</code></pre>
</div>


<p>可以直接调用<code>Expr::getType</code>接口，用于获取指定表达式最终的类型，基于此我们可以检查复杂表达式中的类型转换，例如：</p>

<div class="highlight">
<pre><code class="c"><span class="kt">float</span> <span class="n">f</span> <span class="o">=</span> <span class="mf">2.0f</span><span class="p">;</span>
<span class="kt">double</span> <span class="n">d</span> <span class="o">=</span> <span class="mf">1.0</span><span class="p">;</span>
<span class="n">f</span> <span class="o">=</span> <span class="n">d</span> <span class="o">*</span> <span class="n">f</span><span class="p">;</span> <span class="cm">/* 检查此表达式 */</span>
</code></pre>
</div>


<p>对以上表达式的检查有很多方法，你可以实现MyASTVisitor::VisitBinaryOperator（只要是二目运算符都会调用），或者MyASTVisitor::VisitBinAssign（赋值运算=调用）。无论哪种方式，我们都可以提供一个递归检查两个表达式类型是否相同的接口：</p>

<div class="highlight">
<pre><code class="c"><span class="kt">bool</span> <span class="nf">HasDiffType</span><span class="p">(</span><span class="n">BinaryOperator</span> <span class="o">*</span><span class="n">stmt</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">Expr</span> <span class="o">*</span><span class="n">lhs</span> <span class="o">=</span> <span class="n">stmt</span><span class="o">-&gt;</span><span class="n">getLHS</span><span class="p">()</span><span class="o">-&gt;</span><span class="n">IgnoreImpCasts</span><span class="p">();</span> <span class="c1">// 忽略隐式转换</span>
    <span class="n">Expr</span> <span class="o">*</span><span class="n">rhs</span> <span class="o">=</span> <span class="n">stmt</span><span class="o">-&gt;</span><span class="n">getRHS</span><span class="p">()</span><span class="o">-&gt;</span><span class="n">IgnoreImpCasts</span><span class="p">();</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">lhs</span><span class="o">-&gt;</span><span class="n">getType</span><span class="p">()</span> <span class="o">==</span> <span class="n">rhs</span><span class="o">-&gt;</span><span class="n">getType</span><span class="p">()))</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">isa</span><span class="o">&lt;</span><span class="n">BinaryOperator</span><span class="o">&gt;</span><span class="p">(</span><span class="n">lhs</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="n">HasDiffType</span><span class="p">(</span><span class="n">cast</span><span class="o">&lt;</span><span class="n">BinaryOperator</span><span class="o">&gt;</span><span class="p">(</span><span class="n">lhs</span><span class="p">)))</span>
            <span class="k">return</span> <span class="nb">true</span><span class="p">;</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">isa</span><span class="o">&lt;</span><span class="n">BinaryOperator</span><span class="o">&gt;</span><span class="p">(</span><span class="n">rhs</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="n">HasDiffType</span><span class="p">(</span><span class="n">cast</span><span class="o">&lt;</span><span class="n">BinaryOperator</span><span class="o">&gt;</span><span class="p">(</span><span class="n">rhs</span><span class="p">)))</span>
            <span class="k">return</span> <span class="nb">true</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="k">return</span> <span class="nb">true</span><span class="p">;</span>
<span class="p">}</span>
</code></pre>
</div>


<p>（注：此函数只是简单实现，未考虑类型修饰符之类的问题）</p>

<p>该函数获得二目运算表达式的两个子表达式，然后递归检测这两个表达式的类型是否相同。</p>

<p><code>Expr</code>类提供了更多方便的类型相关的接口，例如判定该表达式是否为常数，是否是布尔表达式，甚至在某些情况下可以直接计算得到值。例如我们可以检查明显的死循环:</p>

<div class="highlight">
<pre><code class="c"><span class="k">while</span> <span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span> <span class="p">}</span>
</code></pre>
</div>


<p>可以使用：</p>

<div class="highlight">
<pre><code class="c"><span class="n">ASTContext</span> <span class="o">&amp;</span><span class="n">context</span> <span class="o">=</span> <span class="n">inst</span><span class="p">.</span><span class="n">GetASTContext</span><span class="p">();</span>
<span class="kt">bool</span> <span class="n">result</span><span class="p">;</span>
<span class="c1">// 假设stmt为WhileStmt</span>
<span class="k">if</span> <span class="p">(</span><span class="n">stmt</span><span class="o">-&gt;</span><span class="n">getCond</span><span class="p">()</span><span class="o">-&gt;</span><span class="n">EvaluateAsBooleanCondition</span><span class="p">(</span><span class="n">result</span><span class="p">,</span> <span class="n">context</span><span class="p">))</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">result</span><span class="p">)</span> 
        <span class="cm">/* 死循环 */</span>
</code></pre>
</div>


<h2>符号表</h2>

<p>符号表这个概念比较广义，这里我仅指的是用于保存类型和变量信息的表。Clang中没有显示的符号表数据结构，但每一个定义都有一个<code>DeclContext</code>，<code>DeclContext</code>用于描述一个定义的上下文环境。有一个特殊的<code>DeclContext</code>被称为<code>translation unit decl</code>，其实也就是全局环境。利用这个translation unit decl，我们可以获取一些全局符号，例如全局变量、全局类型：</p>

<div class="highlight">
<pre><code class="c"><span class="c1">// 获取全局作用域里指定名字的符号列表</span>
<span class="n">DeclContext</span><span class="o">::</span><span class="n">lookup_result</span> <span class="n">GetGlobalDecl</span><span class="p">(</span><span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="o">&amp;</span><span class="n">name</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">ASTContext</span> <span class="o">&amp;</span><span class="n">context</span> <span class="o">=</span> <span class="n">CompilerInst</span><span class="o">::</span><span class="n">getSingleton</span><span class="p">().</span><span class="n">GetASTContext</span><span class="p">();</span>
    <span class="n">DeclContext</span> <span class="o">*</span><span class="n">tcxt</span> <span class="o">=</span> <span class="n">context</span><span class="p">.</span><span class="n">getTranslationUnitDecl</span><span class="p">();</span>
    <span class="n">IdentifierInfo</span> <span class="o">&amp;</span><span class="n">id</span> <span class="o">=</span> <span class="n">context</span><span class="p">.</span><span class="n">Idents</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="n">name</span><span class="p">);</span>
    <span class="k">return</span> <span class="n">tcxt</span><span class="o">-&gt;</span><span class="n">lookup</span><span class="p">(</span><span class="n">DeclarationName</span><span class="p">(</span><span class="o">&amp;</span><span class="n">id</span><span class="p">));</span>
<span class="p">}</span>

<span class="c1">// 可以根据GetGlobalDecl的返回结果，检查该列表里是否有特定的定义，例如函数定义、类型定义等</span>
<span class="kt">bool</span> <span class="n">HasSpecDecl</span><span class="p">(</span><span class="n">DeclContext</span><span class="o">::</span><span class="n">lookup_result</span> <span class="n">ret</span><span class="p">,</span> <span class="n">Decl</span><span class="o">::</span><span class="n">Kind</span> <span class="n">kind</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">for</span> <span class="p">(</span><span class="kt">size_t</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="n">ret</span><span class="p">.</span><span class="n">size</span><span class="p">();</span> <span class="o">++</span><span class="n">i</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">NamedDecl</span> <span class="o">*</span><span class="n">decl</span> <span class="o">=</span> <span class="n">ret</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">decl</span><span class="o">-&gt;</span><span class="n">getKind</span><span class="p">()</span> <span class="o">==</span> <span class="n">kind</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">return</span> <span class="nb">true</span><span class="p">;</span>
        <span class="p">}</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
<span class="p">}</span>
</code></pre>
</div>


<p>有了以上两个函数，我们要检测全局作用域里是否有名为”var”的变量定义，就可以：</p>

<div class="highlight">
<pre><code class="c"><span class="n">HasSpecDecl</span><span class="p">(</span><span class="n">GetGlobalDecl</span><span class="p">(</span><span class="s">"var"</span><span class="p">),</span> <span class="n">Decl</span><span class="o">::</span><span class="n">Var</span><span class="p">);</span>
</code></pre>
</div>


<p>每一个<code>Decl</code>都有对应的<code>DeclContext</code>，要检查相同作用域是否包含相同名字的符号，其处理方式和全局的方式有点不一样：</p>

<div class="highlight">
<pre><code class="c"><span class="c1">// 检查在ctx中是否有与decl同名的符号定义</span>
<span class="kt">bool</span> <span class="nf">HasSymbolInContext</span><span class="p">(</span><span class="k">const</span> <span class="n">NamedDecl</span> <span class="o">*</span><span class="n">decl</span><span class="p">,</span> <span class="k">const</span> <span class="n">DeclContext</span> <span class="o">*</span><span class="n">ctx</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">for</span> <span class="p">(</span><span class="n">DeclContext</span><span class="o">::</span><span class="n">decl_iterator</span> <span class="n">it</span> <span class="o">=</span> <span class="n">ctx</span><span class="o">-&gt;</span><span class="n">decls_begin</span><span class="p">();</span> <span class="n">it</span> <span class="o">!=</span> <span class="n">ctx</span><span class="o">-&gt;</span><span class="n">decls_end</span><span class="p">();</span> <span class="o">++</span><span class="n">it</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">Decl</span> <span class="o">*</span><span class="n">d</span> <span class="o">=</span> <span class="o">*</span><span class="n">it</span><span class="p">;</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">d</span> <span class="o">!=</span> <span class="n">decl</span> <span class="o">&amp;&amp;</span> <span class="n">isa</span><span class="o">&lt;</span><span class="n">NamedDecl</span><span class="o">&gt;</span><span class="p">(</span><span class="n">d</span><span class="p">)</span> <span class="o">&amp;&amp;</span> 
            <span class="n">cast</span><span class="o">&lt;</span><span class="n">NamedDecl</span><span class="o">&gt;</span><span class="p">(</span><span class="n">d</span><span class="p">)</span><span class="o">-&gt;</span><span class="n">getNameAsString</span><span class="p">()</span> <span class="o">==</span> <span class="n">decl</span><span class="o">-&gt;</span><span class="n">getNameAsString</span><span class="p">())</span>
            <span class="k">return</span> <span class="nb">true</span><span class="p">;</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="nf">HasSymbolInContext</span><span class="p">(</span><span class="k">const</span> <span class="n">NamedDecl</span> <span class="o">*</span><span class="n">decl</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="n">HasSymbolInContext</span><span class="p">(</span><span class="n">decl</span><span class="p">,</span> <span class="n">decl</span><span class="o">-&gt;</span><span class="n">getDeclContext</span><span class="p">());</span>
<span class="p">}</span>
</code></pre>
</div>


<p>可以看出，这里检查相同作用域的方式是遍历上下文环境中的所有符号，但对于全局作用域却是直接查找。对于<code>DeclContext</code>的详细信息我也不甚明了，只能算凑合使用。实际上，这里使用“作用域”一词并不准确，在C语言中的作用域概念，和这里的<code>context</code>概念在Clang中并非等同。</p>

<p>如果要检查嵌套作用域里不能定义相同名字的变量，例如：</p>

<div class="highlight">
<pre><code class="c"><span class="kt">int</span> <span class="n">var</span><span class="p">;</span>
<span class="p">{</span>
    <span class="kt">int</span> <span class="n">var</span><span class="p">;</span>
<span class="p">}</span>
</code></pre>
</div>


<p>通过Clang现有的API是无法实现的。因为Clang给上层的语法树结构中，并不包含作用域信息（在Clang的实现中，用于语义分析的类Sema实际上有作用域的处理）。当然，为了实现这个检测，我们可以手动构建作用域信息（通过TraverseCompoundStmt）。</p>

<h2>宏</h2>

<p>宏的处理属于预处理阶段，并不涵盖在语法分析阶段，所以通过Clang的语法树相关接口是无法处理的。跟宏相关的接口，都是通过Clang的<code>Preprocessor</code>相关接口。Clang为此提供了相应的处理机制，上层需要往<code>Preprocessor</code>对象中添加回调对象，例如：</p>

<div class="highlight">
<pre><code class="c"><span class="n">class</span> <span class="n">MyPPCallback</span> <span class="o">:</span> <span class="n">public</span> <span class="n">PPCallbacks</span> <span class="p">{</span>
<span class="nl">public:</span>
    <span class="c1">// 处理#include</span>
    <span class="n">virtual</span> <span class="kt">void</span> <span class="n">InclusionDirective</span><span class="p">(</span><span class="n">SourceLocation</span> <span class="n">HashLoc</span><span class="p">,</span> <span class="k">const</span> <span class="n">Token</span> <span class="o">&amp;</span><span class="n">IncludeTok</span><span class="p">,</span>
        <span class="n">StringRef</span> <span class="n">FileName</span><span class="p">,</span> <span class="kt">bool</span> <span class="n">IsAngled</span><span class="p">,</span> <span class="n">CharSourceRange</span> <span class="n">FilenameRange</span><span class="p">,</span>
        <span class="k">const</span> <span class="n">FileEntry</span> <span class="o">*</span><span class="n">File</span><span class="p">,</span> <span class="n">StringRef</span> <span class="n">SearchPath</span><span class="p">,</span> <span class="n">StringRef</span> <span class="n">RelativePath</span><span class="p">,</span> <span class="k">const</span> <span class="n">Module</span> <span class="o">*</span><span class="n">Imported</span><span class="p">)</span> <span class="p">{</span>
    <span class="p">}</span>

    <span class="c1">// 处理#define</span>
    <span class="n">virtual</span> <span class="kt">void</span> <span class="n">MacroDefined</span><span class="p">(</span><span class="k">const</span> <span class="n">Token</span> <span class="o">&amp;</span><span class="n">MacroNameTok</span><span class="p">,</span> <span class="k">const</span> <span class="n">MacroInfo</span> <span class="o">*</span><span class="n">MI</span><span class="p">)</span> <span class="p">{</span>
    <span class="p">}</span>

    <span class="n">virtual</span> <span class="kt">void</span> <span class="n">MacroUndefined</span><span class="p">(</span><span class="k">const</span> <span class="n">Token</span> <span class="o">&amp;</span><span class="n">MacroNameTok</span><span class="p">,</span> <span class="k">const</span> <span class="n">MacroInfo</span> <span class="o">*</span><span class="n">MI</span><span class="p">)</span> <span class="p">{</span>
    <span class="p">}</span> 
<span class="p">}</span>

<span class="n">inst</span><span class="p">.</span><span class="n">getPreprocessor</span><span class="p">().</span><span class="n">addPPCallbacks</span><span class="p">(</span><span class="n">new</span> <span class="n">MyPPCallback</span><span class="p">());</span>
</code></pre>
</div>


<p>即，通过实现<code>PPCallbacks</code>中对应的接口，就可以获得处理宏的通知。</p>

<p>Clang使用MacroInfo去表示一个宏。MacroInfo将宏体以一堆token来保存，例如我们要检测宏体中使用<code>##</code>和<code>#</code>的情况，则只能遍历这些tokens:</p>

<div class="highlight">
<pre><code class="c"><span class="c1">// 分别记录#和##在宏体中使用的数量</span>
<span class="kt">int</span> <span class="n">hash</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="n">hashhash</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="n">MacroInfo</span><span class="o">::</span><span class="n">tokens_iterator</span> <span class="n">it</span> <span class="o">=</span> <span class="n">MI</span><span class="o">-&gt;</span><span class="n">tokens_begin</span><span class="p">();</span> <span class="n">it</span> <span class="o">!=</span> <span class="n">MI</span><span class="o">-&gt;</span><span class="n">tokens_end</span><span class="p">();</span> <span class="o">++</span><span class="n">it</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">const</span> <span class="n">Token</span> <span class="o">&amp;</span><span class="n">token</span> <span class="o">=</span> <span class="o">*</span><span class="n">it</span><span class="p">;</span>
    <span class="n">hash</span> <span class="o">+=</span> <span class="p">(</span><span class="n">token</span><span class="p">.</span><span class="n">getKind</span><span class="p">()</span> <span class="o">==</span> <span class="n">tok</span><span class="o">::</span><span class="n">hash</span> <span class="o">?</span> <span class="mi">1</span> <span class="o">:</span> <span class="mi">0</span><span class="p">);</span>
    <span class="n">hashhash</span> <span class="o">+=</span> <span class="p">(</span><span class="n">token</span><span class="p">.</span><span class="n">getKind</span><span class="p">()</span> <span class="o">==</span> <span class="n">tok</span><span class="o">::</span><span class="n">hashhash</span> <span class="o">?</span> <span class="mi">1</span> <span class="o">:</span> <span class="mi">0</span><span class="p">);</span>
<span class="p">}</span>
</code></pre>
</div>


<h2>其他</h2>

<p>在我们所支持的编程规范中，有些规范是难以支持的，因此我使用了一些蹩脚的方式来实现。</p>

<h3>手工解析</h3>

<p>在针对函数的参数定义方面，我们支持的规范要求不能定义参数为空的函数，如果该函数没有参数，则必须以<code>void</code>显示标识，例如：</p>

<div class="highlight">
<pre><code class="c"><span class="kt">int</span> <span class="nf">func</span><span class="p">();</span> <span class="cm">/* 非法 */</span>
<span class="kt">int</span> <span class="nf">func</span><span class="p">(</span><span class="kt">void</span><span class="p">);</span> <span class="cm">/* 合法 */</span>
</code></pre>
</div>


<p>对于Clang而言，函数定义（或声明）使用的是<code>FunctionDecl</code>，而Clang记录的信息仅包括该函数是否有参数，参数个数是多少，并不记录当其参数个数为0时是否使用<code>void</code>来声明（记录下来没多大意义）。解决这个问题的办法，可以通过<code>SourceLocation</code>获取到对应源代码中的文本内容，然后对此文本内容做手工分析即可。</p>

<p>（注：<code>SourceLocation</code>是Clang中用于表示源代码位置的类，包括行号和列号，所有<code>Stmt</code>都会包含此信息）</p>

<p>通过<code>SourceLocation</code>获取对应源码的内容：</p>

<div class="highlight">
<pre><code class="c"><span class="n">std</span><span class="o">::</span><span class="n">pair</span><span class="o">&lt;</span><span class="n">FileID</span><span class="p">,</span> <span class="kt">unsigned</span><span class="o">&gt;</span> <span class="n">locInfo</span> <span class="o">=</span> <span class="n">SM</span><span class="p">.</span><span class="n">getDecomposedLoc</span><span class="p">(</span><span class="n">loc</span><span class="p">);</span>
<span class="kt">bool</span> <span class="n">invalidTemp</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>
<span class="n">llvm</span><span class="o">::</span><span class="n">StringRef</span> <span class="n">file</span> <span class="o">=</span> <span class="n">SM</span><span class="p">.</span><span class="n">getBufferData</span><span class="p">(</span><span class="n">locInfo</span><span class="p">.</span><span class="n">first</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">invalidTemp</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">invalidTemp</span><span class="p">)</span>
    <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
<span class="c1">// tokenBegin即为loc对应源码内容的起始点</span>
<span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">tokenBegin</span> <span class="o">=</span> <span class="n">file</span><span class="p">.</span><span class="n">data</span><span class="p">()</span> <span class="o">+</span> <span class="n">locInfo</span><span class="p">.</span><span class="n">second</span><span class="p">;</span>
</code></pre>
</div>


<p>要手工分析这些内容实际上还是有点繁杂，为此我们可以直接使用Clang中词法分析相关的组件来完成这件事：</p>

<div class="highlight">
<pre><code class="c"><span class="n">Lexer</span> <span class="o">*</span><span class="n">lexer</span> <span class="o">=</span> <span class="n">new</span> <span class="n">Lexer</span><span class="p">(</span><span class="n">SM</span><span class="p">.</span><span class="n">getLocForStartOfFile</span><span class="p">(</span><span class="n">locInfo</span><span class="p">.</span><span class="n">first</span><span class="p">),</span> <span class="n">opts</span><span class="p">,</span> <span class="n">file</span><span class="p">.</span><span class="n">begin</span><span class="p">(),</span> <span class="n">tokenBegin</span><span class="p">,</span> <span class="n">file</span><span class="p">.</span><span class="n">end</span><span class="p">());</span>
<span class="n">Token</span> <span class="n">tok</span><span class="p">;</span>
<span class="n">lexer</span><span class="o">-&gt;</span><span class="n">Lex</span><span class="p">(</span><span class="n">tok</span><span class="p">);</span> <span class="c1">// 取得第一个tok，反复调用可以获取一段token流</span>
</code></pre>
</div>


<h3>Diagnostic</h3>

<p>Clang中用Diagnostic来进行编译错误的提示。每一个编译错误（警告、建议等）都会有一段文字描述，这些文字描述为了支持多国语言，使用了一种ID的表示方法。总之，对于一个特定的编译错误提示而言，其diagnostic ID是固定的。</p>

<p>在我们的规范中，有些规范检测的代码在Clang中会直接编译出错，例如函数调用传递的参数个数不等于函数定义时的形参个数。当Clang编译出错时，其语法树实际上是不完善的。解决此问题的最简单办法，就是通过diagnostic实现。也就是说，我是通过将我们的特定规范映射到特定的diagnostic，当发生这个特定的编译错误时，就可以认定该规范实际上被检测到。对于简单的情况而言，这样的手段还算奏效。</p>

<div class="highlight">
<pre><code class="c"><span class="c1">// `TextDiagnosticPrinter`可以将错误信息打印在控制台上，为了调试方便我从它派生而来</span>
<span class="n">class</span> <span class="n">MyDiagnosticConsumer</span> <span class="o">:</span> <span class="n">public</span> <span class="n">TextDiagnosticPrinter</span> <span class="p">{</span>
<span class="nl">public:</span>
    <span class="c1">// 当一个错误发生时，会调用此函数，我会在这个函数里通过Info.getID()取得Diagnostic ID，然后对应地取出规范ID</span>
    <span class="n">virtual</span> <span class="kt">void</span> <span class="n">HandleDiagnostic</span><span class="p">(</span><span class="n">DiagnosticsEngine</span><span class="o">::</span><span class="n">Level</span> <span class="n">DiagLevel</span><span class="p">,</span>
        <span class="k">const</span> <span class="n">Diagnostic</span> <span class="o">&amp;</span><span class="n">Info</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">TextDiagnosticPrinter</span><span class="o">::</span><span class="n">HandleDiagnostic</span><span class="p">(</span><span class="n">DiagLevel</span><span class="p">,</span> <span class="n">Info</span><span class="p">);</span>
        <span class="c1">// 例如检查三字母词(trigraph)的使用</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">Info</span><span class="p">.</span><span class="n">getID</span><span class="p">()</span> <span class="o">==</span> <span class="mi">816</span><span class="p">)</span>
            <span class="cm">/* 报告使用了三字母词 */</span>
    <span class="p">}</span>
<span class="p">};</span>

<span class="c1">// 初始化时需传入自己定义的diagnostic</span>
<span class="n">inst</span><span class="p">.</span><span class="n">createDiagnostics</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">new</span> <span class="n">MyDiagnosticConsumer</span><span class="p">(</span><span class="o">&amp;</span><span class="n">inst</span><span class="p">.</span><span class="n">getDiagnosticOpts</span><span class="p">()));</span>
</code></pre>
</div>


<p>该例子代码演示了对三字母词(<a href="http://en.wikipedia.org/wiki/Digraphs_and_trigraphs">wiki trigraph</a>)使用限制的规范检测。</p>

<p>全文完。</p>

<p class="post-footer">
            原文地址：
            <a href="http://codemacro.com/2013/02/12/using-clang/">http://codemacro.com/2013/02/12/using-clang/</a><br>
            written by <a href="http://codemacro.com">Kevin Lynx</a>
             posted at <a href="http://codemacro.com">http://codemacro.com</a>
            </p>

</div><img src ="http://www.cppblog.com/kevinlynx/aggbug/197810.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/kevinlynx/" target="_blank">Kevin Lynx</a> 2013-02-12 21:53 <a href="http://www.cppblog.com/kevinlynx/archive/2013/02/12/197810.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C++陷阱：构造函数中的多态</title><link>http://www.cppblog.com/kevinlynx/archive/2012/09/17/190984.html</link><dc:creator>Kevin Lynx</dc:creator><author>Kevin Lynx</author><pubDate>Mon, 17 Sep 2012 08:30:00 GMT</pubDate><guid>http://www.cppblog.com/kevinlynx/archive/2012/09/17/190984.html</guid><wfw:comment>http://www.cppblog.com/kevinlynx/comments/190984.html</wfw:comment><comments>http://www.cppblog.com/kevinlynx/archive/2012/09/17/190984.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/kevinlynx/comments/commentRss/190984.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/kevinlynx/services/trackbacks/190984.html</trackback:ping><description><![CDATA[<div class="entry-content">
<p>C++中主要是通过给函数加上<code>virtual</code>关键字来实现多态。多态可用于改变一个接口的实现，也算是一种嵌入应用层代码到底层的实现手段。就算你用不到C++那些复杂的技术，多态肯定会被用到。</p>

<p>但加上<code>virtual</code>不一定能保证多态成功：</p>

<!-- more -->




<div class="highlight">
<pre><code class="c"><span class="cp">#include &lt;stdio.h&gt;</span>

<span class="n">class</span> <span class="n">Base</span> <span class="p">{</span>
<span class="nl">public:</span>
    <span class="n">Base</span><span class="p">()</span> <span class="p">{</span>
        <span class="n">Init</span><span class="p">();</span>
    <span class="p">}</span>

    <span class="k">virtual</span> <span class="o">~</span><span class="n">Base</span><span class="p">()</span> <span class="p">{</span>
        <span class="n">Release</span><span class="p">();</span>
    <span class="p">}</span>

    <span class="k">virtual</span> <span class="kt">void</span> <span class="n">Init</span><span class="p">()</span> <span class="p">{</span>
        <span class="n">printf</span><span class="p">(</span><span class="s">"Base::Init</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">virtual</span> <span class="kt">void</span> <span class="n">Release</span><span class="p">()</span> <span class="p">{</span>
        <span class="n">printf</span><span class="p">(</span><span class="s">"Base::Release</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">};</span>

<span class="n">class</span> <span class="n">Derived</span> <span class="o">:</span> <span class="n">public</span> <span class="n">Base</span> <span class="p">{</span>
<span class="nl">public:</span>
    <span class="k">virtual</span> <span class="kt">void</span> <span class="n">Init</span><span class="p">()</span> <span class="p">{</span>
        <span class="n">printf</span><span class="p">(</span><span class="s">"Derived::Init</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">virtual</span> <span class="kt">void</span> <span class="n">Release</span><span class="p">()</span> <span class="p">{</span>
        <span class="n">printf</span><span class="p">(</span><span class="s">"Derived:Release</span><span class="se">\n</span><span class="s">"</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">Base</span> <span class="o">*</span><span class="n">obj</span> <span class="o">=</span> <span class="n">new</span> <span class="n">Derived</span><span class="p">();</span>
    <span class="n">delete</span> <span class="n">obj</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>当在构造函数，包括析构函数中调用<code>virtual</code>函数时，预想中的多态是无法完成的，以上代码输出结果为：</p>

<div class="highlight">
<pre><code class="c"><span class="n">Base</span><span class="o">::</span><span class="n">Init</span>
<span class="n">Base</span><span class="o">::</span><span class="n">Release</span>
</code></pre>
</div>


<p>从语言设计角度来看，我个人是不接受这种行为的。我觉得对一门语言而言，几乎所有特性都应该是一致的，不应该或尽量少地出现这种&#8220;例外&#8220;。如果我构造一个对象，让它以不同的方式被构造，这和改变它的某个行为有什么区别？（从这句话来看，似乎还真有区别）</p>

<p>当然，从语言实现来看，这样的运行结果又似乎是必然的。因为，基类的构造是早于派生类的（作为其一部分），只有当构造完派生类后，其用于支持多态的虚表才会被正确构造。也就是说，在基类中调用虚函数时，既然虚表都为正确构造，自然调用的不会是派生类的虚函数了。析构函数按照析构的顺序来看，也会面临同样的情况。</p>

<p class="post-footer">
            原文地址：
            <a href="http://codemacro.com/2012/09/17/c-plus-plus-ctor-virtual/">http://codemacro.com/2012/09/17/c-plus-plus-ctor-virtual/</a><br />
            written by <a href="http://codemacro.com">Kevin Lynx</a>
            &nbsp;posted at <a href="http://codemacro.com">http://codemacro.com</a>
            </p>

</div><img src ="http://www.cppblog.com/kevinlynx/aggbug/190984.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/kevinlynx/" target="_blank">Kevin Lynx</a> 2012-09-17 16:30 <a href="http://www.cppblog.com/kevinlynx/archive/2012/09/17/190984.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C++陷阱：virtual析构函数</title><link>http://www.cppblog.com/kevinlynx/archive/2012/09/13/190541.html</link><dc:creator>Kevin Lynx</dc:creator><author>Kevin Lynx</author><pubDate>Thu, 13 Sep 2012 09:31:00 GMT</pubDate><guid>http://www.cppblog.com/kevinlynx/archive/2012/09/13/190541.html</guid><wfw:comment>http://www.cppblog.com/kevinlynx/comments/190541.html</wfw:comment><comments>http://www.cppblog.com/kevinlynx/archive/2012/09/13/190541.html#Feedback</comments><slash:comments>7</slash:comments><wfw:commentRss>http://www.cppblog.com/kevinlynx/comments/commentRss/190541.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/kevinlynx/services/trackbacks/190541.html</trackback:ping><description><![CDATA[<div class="entry-content">
<p>有一天有个同事在通过vld调试一个内存泄漏问题，折腾了很久然后找到我。我瞥了一眼他的代码，发现问题和我曾经遇到的一模一样：</p>

<div class="highlight">
<pre><code class="c"><span class="n">class</span> <span class="n">Base</span> <span class="p">{</span>
<span class="nl">public:</span>
    <span class="o">~</span><span class="n">Base</span><span class="p">();</span>
<span class="p">};</span>

<span class="n">class</span> <span class="n">Derived</span> <span class="o">:</span> <span class="n">public</span> <span class="n">Base</span> <span class="p">{</span>
<span class="nl">privated:</span>
    <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span> <span class="n">m_data</span><span class="p">;</span>    <br /><span class="p">};</span>

<span class="n">Base</span> <span class="o">*</span><span class="n">obj</span> <span class="o">=</span> <span class="n">new</span> <span class="n">Derived</span><span class="p">();</span>
<span class="n">delete</span> <span class="n">obj</span><span class="p">;</span>
</code></pre>
</div>


<p>当然，实际代码比这个复杂得多(这也是导致从发现问题到找到问题耗费大量时间的原因)。vld在报内存泄漏时，当然报的位置是<code>new</code>的地方。这个同事检查了这个对象的整个生命周期，确定他正确地释放了这个对象。</p>

<p>问题的关键就在于：<strong><code>Base</code>类的析构函数不是<code>virtual</code>的</strong>。因为不是<code>virtual</code>，所以在对一个<code>Base</code>类型的指针进行<code>delete</code>时，就不会调用到派生类<code>Derived</code>的析构函数。而派生类里的析构函数会用于析构其内部的子对象，也就是这里的<code>m_data</code>。这样，就造成了内存泄漏。</p>

<p>这其实是一个很低级的失误。但毫不客气地说C++中有很多这种少个关键字或者代码位置不对就会造成另一个结果的例子。事实上，针对这些悲剧也有很多书提出一些准则来让大家去无脑遵守。例如针对这个例子，我就记得曾有书说，只要你觉得你的类会被继承，那么最好给析构函数加上virtual。</p>

<p class="post-footer">
            原文地址：
            <a href="http://codemacro.com/2012/09/13/c-plus-plus-virtual-destructor/">http://codemacro.com/2012/09/13/c-plus-plus-virtual-destructor/</a><br />
            written by <a href="http://codemacro.com">Kevin Lynx</a>
            &nbsp;posted at <a href="http://codemacro.com">http://codemacro.com</a>
            </p>

</div><img src ="http://www.cppblog.com/kevinlynx/aggbug/190541.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/kevinlynx/" target="_blank">Kevin Lynx</a> 2012-09-13 17:31 <a href="http://www.cppblog.com/kevinlynx/archive/2012/09/13/190541.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C/c++中几种操作位的方法</title><link>http://www.cppblog.com/kevinlynx/archive/2012/09/04/189464.html</link><dc:creator>Kevin Lynx</dc:creator><author>Kevin Lynx</author><pubDate>Tue, 04 Sep 2012 12:29:00 GMT</pubDate><guid>http://www.cppblog.com/kevinlynx/archive/2012/09/04/189464.html</guid><wfw:comment>http://www.cppblog.com/kevinlynx/comments/189464.html</wfw:comment><comments>http://www.cppblog.com/kevinlynx/archive/2012/09/04/189464.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://www.cppblog.com/kevinlynx/comments/commentRss/189464.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/kevinlynx/services/trackbacks/189464.html</trackback:ping><description><![CDATA[<div class="entry-content">
<p>参考<a href="http://stackoverflow.com/questions/47981/how-do-you-set-clear-and-toggle-a-single-bit-in-c">How do you set, clear and toggle a single bit in C?</a></p>

<p>c/c++中对二进制位的操作包括设置某位为1、清除某位（置为0）、开关某位(toggling a bit)、检查某位是否为1等。这些操作较为常见并且可以作为其他位运算的基础接口，以下罗列几种方法：</p>

<h2>传统方法</h2>

<ul>
<li>设置某位为1</li>
</ul>
<div class="highlight">
<pre><code class="c"><span class="n">number</span> <span class="o">|=</span> <span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="n">x</span><span class="p">;</span> <span class="c1">// 设置第x位为1</span>
</code></pre>
</div>




<!-- more -->


<ul>
<li>清除某位</li>
</ul>
<div class="highlight">
<pre><code class="c"><span class="n">number</span> <span class="o">&amp;=</span> <span class="o">~</span><span class="p">(</span><span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="n">x</span><span class="p">);</span> <span class="c1">// 置第x位为0</span>
</code></pre>
</div>


<ul>
<li>开关某位</li>
</ul>
<div class="highlight">
<pre><code class="c"><span class="n">number</span> <span class="o">^=</span> <span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="n">x</span><span class="p">;</span>
</code></pre>
</div>


<ul>
<li>检查某位</li>
</ul>
<div class="highlight">
<pre><code class="c"><span class="k">if</span> <span class="p">(</span><span class="n">number</span> <span class="o">&amp;</span> <span class="p">(</span><span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="n">x</span><span class="p">))</span>
</code></pre>
</div>


<p>相应地我们可以将其封装起来，简便的方法是使用宏来封装：</p>

<div class="highlight">
<pre><code class="c"><span class="cp">#define BIT_SET(a,b) ((a) |= (1&lt;&lt;(b)))</span>
<span class="cp">#define BIT_CLEAR(a,b) ((a) &amp;= ~(1&lt;&lt;(b)))</span>
<span class="cp">#define BIT_FLIP(a,b) ((a) ^= (1&lt;&lt;(b)))</span>
<span class="cp">#define BIT_CHECK(a,b) ((a) &amp; (1&lt;&lt;(b)))</span>
</code></pre>
</div>


<h2>使用位结构操作</h2>

<p>这个使用起来简单很多：</p>

<div class="highlight">
<pre><code class="c"><span class="k">struct</span> <span class="n">bits</span> <span class="p">{</span>
    <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">a</span><span class="o">:</span><span class="mi">1</span><span class="p">;</span>
    <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">b</span><span class="o">:</span><span class="mi">1</span><span class="p">;</span>
    <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">c</span><span class="o">:</span><span class="mi">1</span><span class="p">;</span>
<span class="p">};</span>

<span class="k">struct</span> <span class="n">bits</span> <span class="n">mybits</span><span class="p">;</span>

<span class="c1">// set/clear a bit</span>
<span class="n">mybits</span><span class="p">.</span><span class="n">b</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
<span class="n">mybits</span><span class="p">.</span><span class="n">c</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>

<span class="c1">// toggle a bit</span>
<span class="n">mybits</span><span class="p">.</span><span class="n">a</span> <span class="o">=</span> <span class="o">!</span><span class="n">mybits</span><span class="p">.</span><span class="n">a</span><span class="p">;</span>
<span class="n">mybits</span><span class="p">.</span><span class="n">b</span> <span class="o">=</span> <span class="o">~</span><span class="n">mybits</span><span class="p">.</span><span class="n">b</span><span class="p">;</span>
<span class="n">mybits</span><span class="p">.</span><span class="n">c</span> <span class="o">^=</span> <span class="mi">1</span><span class="p">;</span>

<span class="c1">// check a bit</span>
<span class="k">if</span> <span class="p">(</span><span class="n">mybits</span><span class="p">.</span><span class="n">c</span><span class="p">)</span>
</code></pre>
</div>


<h2>使用STL的std::bitset<n></n>
</h2>

<p>这个方法其实类似于使用位结构，只不过STL包装了这个结构定义，当然还提供了很多便捷的接口：</p>

<div class="highlight">
<pre><code class="c"><span class="n">std</span><span class="o">::</span><span class="n">bitset</span><span class="o">&lt;</span><span class="mi">5</span><span class="o">&gt;</span> <span class="n">bits</span><span class="p">;</span>
<span class="n">bits</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>
<span class="n">bits</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>
<span class="n">bits</span><span class="p">.</span><span class="n">set</span><span class="p">(</span><span class="mi">2</span><span class="p">);</span>
<span class="n">bits</span><span class="p">.</span><span class="n">flip</span><span class="p">(</span><span class="mi">3</span><span class="p">);</span>
<span class="n">bits</span><span class="p">.</span><span class="n">reset</span><span class="p">(</span><span class="mi">2</span><span class="p">);</span>
</code></pre>
</div>




<p class="post-footer">
            原文地址：
            <a href="http://codemacro.com/2012/09/04/bit-operation-in-c-slash-c-plus-plus/">http://codemacro.com/2012/09/04/bit-operation-in-c-slash-c-plus-plus/</a><br />
            written by <a href="http://codemacro.com">Kevin Lynx</a>
            &nbsp;posted at <a href="http://codemacro.com">http://codemacro.com</a>
            </p>

</div><img src ="http://www.cppblog.com/kevinlynx/aggbug/189464.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/kevinlynx/" target="_blank">Kevin Lynx</a> 2012-09-04 20:29 <a href="http://www.cppblog.com/kevinlynx/archive/2012/09/04/189464.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C/c++中的--&amp;gt;运算符</title><link>http://www.cppblog.com/kevinlynx/archive/2012/09/03/189272.html</link><dc:creator>Kevin Lynx</dc:creator><author>Kevin Lynx</author><pubDate>Mon, 03 Sep 2012 07:30:00 GMT</pubDate><guid>http://www.cppblog.com/kevinlynx/archive/2012/09/03/189272.html</guid><wfw:comment>http://www.cppblog.com/kevinlynx/comments/189272.html</wfw:comment><comments>http://www.cppblog.com/kevinlynx/archive/2012/09/03/189272.html#Feedback</comments><slash:comments>3</slash:comments><wfw:commentRss>http://www.cppblog.com/kevinlynx/comments/commentRss/189272.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/kevinlynx/services/trackbacks/189272.html</trackback:ping><description><![CDATA[<div class="entry-content">
<p>参考<a href="http://stackoverflow.com/questions/1642028/what-is-the-name-of-this-operator">What is the name of this operator: &#8220;&#8211;&gt;&#8221;?</a></p>

<p>c/c++中以下代码是合法的：</p>

<div class="highlight">
<pre><code class="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="kt">int</span> <span class="n">x</span> <span class="o">=</span> <span class="mi">10</span><span class="p">;</span>
     <span class="k">while</span><span class="p">(</span> <span class="n">x</span> <span class="o">--&gt;</span> <span class="mi">0</span> <span class="p">)</span> <span class="c1">// x goes to 0</span>
     <span class="p">{</span>
        <span class="n">printf</span><span class="p">(</span><span class="s">"%d "</span><span class="p">,</span> <span class="n">x</span><span class="p">);</span>
     <span class="p">}</span>
<span class="p">}</span>
</code></pre>
</div>


<p><code>--&gt;</code>是一个合法的操作符，我打赌自认c/c++熟手的你们都不知道这个操作符。有人称它为<code>goes to</code>操作符，<code>x--&gt;0</code>表示x向0趋近。</p>

<p><strong>其实我在忽悠你们。</strong> 并且我相信有很多人对此把戏相当熟悉。没错，<code>--&gt;</code>只是两个操作符恰好遇在了一起，他们是自减运算符<code>--</code>和大于比较运算符<code>&gt;</code>：</p>

<div class="highlight">
<pre><code class="c"><span class="k">while</span> <span class="p">(</span><span class="n">x</span><span class="o">--</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span>
    <span class="p">...</span>
</code></pre>
</div>


<p>类似的把戏还有：</p>

<div class="highlight">
<pre><code class="c"><span class="k">while</span> <span class="p">(</span><span class="n">x</span> <span class="o">--</span> \<br />             \<br />              \<br />               \<br />                <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"%d "</span><span class="p">,</span> <span class="n">x</span><span class="p">);</span>
</code></pre>
</div>




<p class="post-footer">
            原文地址：
            <a href="http://codemacro.com/2012/09/03/goes-to-operator/">http://codemacro.com/2012/09/03/goes-to-operator/</a><br />
            written by <a href="http://codemacro.com">Kevin Lynx</a>
            &nbsp;posted at <a href="http://codemacro.com">http://codemacro.com</a>
            </p>

</div><img src ="http://www.cppblog.com/kevinlynx/aggbug/189272.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/kevinlynx/" target="_blank">Kevin Lynx</a> 2012-09-03 15:30 <a href="http://www.cppblog.com/kevinlynx/archive/2012/09/03/189272.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>为什么处理排序的数组要比非排序的快？</title><link>http://www.cppblog.com/kevinlynx/archive/2012/08/30/188786.html</link><dc:creator>Kevin Lynx</dc:creator><author>Kevin Lynx</author><pubDate>Thu, 30 Aug 2012 09:43:00 GMT</pubDate><guid>http://www.cppblog.com/kevinlynx/archive/2012/08/30/188786.html</guid><wfw:comment>http://www.cppblog.com/kevinlynx/comments/188786.html</wfw:comment><comments>http://www.cppblog.com/kevinlynx/archive/2012/08/30/188786.html#Feedback</comments><slash:comments>3</slash:comments><wfw:commentRss>http://www.cppblog.com/kevinlynx/comments/commentRss/188786.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/kevinlynx/services/trackbacks/188786.html</trackback:ping><description><![CDATA[<div class="entry-content">
<p>参考<a href="http://stackoverflow.com/questions/11227809/why-is-processing-a-sorted-array-faster-than-an-unsorted-array">Why is processing a sorted array faster than an unsorted array?</a></p>

<h2>问题</h2>

<p>看以下代码：</p>

<div class="highlight">
<pre><code class="c"><span class="cp">#include &lt;algorithm&gt;</span>
<span class="cp">#include &lt;ctime&gt;</span>
<span class="cp">#include &lt;iostream&gt;</span>

<span class="kt">int</span> <span class="nf">main</span><span class="p">()</span>
<span class="p">{</span>
    <span class="c1">// generate data</span>
    <span class="k">const</span> <span class="kt">unsigned</span> <span class="n">arraySize</span> <span class="o">=</span> <span class="mi">32768</span><span class="p">;</span>
    <span class="kt">int</span> <span class="n">data</span><span class="p">[</span><span class="n">arraySize</span><span class="p">];</span>

    <span class="k">for</span> <span class="p">(</span><span class="kt">unsigned</span> <span class="n">c</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">c</span> <span class="o">&lt;</span> <span class="n">arraySize</span><span class="p">;</span> <span class="o">++</span><span class="n">c</span><span class="p">)</span>
        <span class="n">data</span><span class="p">[</span><span class="n">c</span><span class="p">]</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">rand</span><span class="p">()</span> <span class="o">%</span> <span class="mi">256</span><span class="p">;</span>


    <span class="c1">// !!! with this, the next loop runs faster</span>
    <span class="n">std</span><span class="o">::</span><span class="n">sort</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="n">data</span> <span class="o">+</span> <span class="n">arraySize</span><span class="p">);</span>


    <span class="c1">// test</span>
    <span class="kt">clock_t</span> <span class="n">start</span> <span class="o">=</span> <span class="n">clock</span><span class="p">();</span>
    <span class="kt">long</span> <span class="kt">long</span> <span class="n">sum</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>

    <span class="k">for</span> <span class="p">(</span><span class="kt">unsigned</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">100000</span><span class="p">;</span> <span class="o">++</span><span class="n">i</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="c1">// primary loop</span>
        <span class="k">for</span> <span class="p">(</span><span class="kt">unsigned</span> <span class="n">c</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">c</span> <span class="o">&lt;</span> <span class="n">arraySize</span><span class="p">;</span> <span class="o">++</span><span class="n">c</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="n">c</span><span class="p">]</span> <span class="o">&gt;=</span> <span class="mi">128</span><span class="p">)</span>
                <span class="n">sum</span> <span class="o">+=</span> <span class="n">data</span><span class="p">[</span><span class="n">c</span><span class="p">];</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="kt">double</span> <span class="n">elapsedTime</span> <span class="o">=</span> <span class="n">static_cast</span><span class="o">&lt;</span><span class="kt">double</span><span class="o">&gt;</span><span class="p">(</span><span class="n">clock</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span><span class="p">)</span> <span class="o">/</span> <span class="n">CLOCKS_PER_SEC</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">elapsedTime</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">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"sum = "</span> <span class="o">&lt;&lt;</span> <span class="n">sum</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>问题就在于，去掉<code>std::sort</code>那一行，以上代码将运行更长的时间。在我的机器上未去掉<code>std::sort</code>耗时8.99s，去掉后耗时24.78s。编译器使用的是gcc4.4.3。事实上，以上代码跟编译器没有关系，甚至跟语言没有关系。那这是为什么呢？</p>

<!-- more -->


<p>这跟处理这个数组的逻辑有非常大的关系。如以上代码所示，这个循环里有个条件判断。条件判断被编译成二进制代码后，就是一个跳转指令，类似：</p>

<p>具体为什么会不同，这涉及到计算机CPU执行指令时的行为。</p>

<h2>CPU的流水线指令执行</h2>

<p>想象现在有一堆指令等待CPU去执行，那么CPU是如何执行的呢？具体的细节可以找一本计算机组成原理的书来看。CPU执行一堆指令时，并不是单纯地一条一条取出来执行，而是按照一种流水线的方式，在CPU真正执行一条指令前，这条指令就像工厂里流水线生产的产品一样，已经被经过一些处理。简单来说，一条指令可能经过这些过程：取指(Fetch)、解码(Decode)、执行(Execute)、放回(Write-back)。</p>

<p>假设现在有指令序列ABCDEFG。当CPU正在执行(execute)指令A时，CPU的其他处理单元（CPU是由若干部件构成的）其实已经预先处理到了指令A后面的指令，例如B可能已经被解码，C已经被取指。这就是流水线执行，这可以保证CPU高效地执行指令。</p>

<h2>Branch Prediction</h2>

<p>如上所说，CPU在执行一堆顺序执行的指令时，因为对于执行指令的部件来说，其基本不需要等待，因为诸如取指、解码这些过程早就被做了。但是，当CPU面临非顺序执行的指令序列时，例如之前提到的跳转指令，情况会怎样呢？</p>

<p>取指、解码这些CPU单元并不知道程序流程会跳转，只有当CPU执行到跳转指令本身时，才知道该不该跳转。所以，取指解码这些单元就会继续取跳转指令之后的指令。当CPU执行到跳转指令时，如果真的发生了跳转，那么之前的预处理（取指、解码）就白做了。这个时候，CPU得从跳转目标处临时取指、解码，然后才开始执行，这意味着：CPU停了若干个时钟周期！</p>

<p>这其实是个问题，如果CPU的设计放任这个问题，那么其速度就很难提升起来。为此，人们发明了一种技术，称为branch prediction，也就是分支预测。分支预测的作用，就是预测某个跳转指令是否会跳转。而CPU就根据自己的预测到目标地址取指令。这样，即可从一定程度提高运行速度。当然，分支预测在实现上有很多方法。</p>

<p>简单的预测可以直接使用之前的实际执行结果。例如某个跳转指令某一次产生了跳转，那么下一次执行该指令时，CPU就直接从跳转目标地址处取指，而不是该跳转指令的下一条指令。</p>

<h2>答案</h2>

<p>了解了以上信息后，文章开头提出的问题就可以解释了。这个代码中有一个循环，这个循环里有一个条件判断。每一次CPU执行这个条件判断时，CPU都可能跳转到循环开始处的指令，即不执行if后的指令。使用分支预测技术，当处理已经排序的数组时，在若干次<code>data[c]&gt;=128</code>都不成立时（或第一次不成立时，取决于分支预测的实现），CPU预测这个分支是始终会跳转到循环开始的指令时，这个时候CPU将保持有效的执行，不需要重新等待到新的地址取指；同样，当<code>data[c]&gt;=128</code>条件成立若干次后，CPU也可以预测这个分支是不必跳转的，那么这个时候CPU也可以保持高效执行。</p>

<p>相反，如果是无序的数组，CPU的分支预测在很大程度上都无法预测成功，基本就是50%的预测成功概率，这将消耗大量的时间，因为CPU很多时间都会等待取指单元重新取指。</p>

<p>本文完。最后感叹下stackoverflow上这个帖子里那个老外回答问题的专业性，我要是楼主早就感动得涕泪横飞了。感谢每一个传播知识的人。</p>

<h2>参考资料</h2>

<ol>
<li><a href="http://blog.sina.com.cn/s/blog_6c673e570100zfmo.html">http://blog.sina.com.cn/s/blog_6c673e570100zfmo.html</a></li>
<li><a href="http://www.cnblogs.com/dongliqian/archive/2012/04/05/2433847.html">http://www.cnblogs.com/dongliqian/archive/2012/04/05/2433847.html</a></li>
<li><a href="http://en.wikipedia.org/wiki/Branch_predictor">http://en.wikipedia.org/wiki/Branch_predictor</a></li>
</ol>
<p class="post-footer">
            原文地址：
            <a href="http://codemacro.com/2012/08/29/branch-predictor/">http://codemacro.com/2012/08/29/branch-predictor/</a><br />
            written by <a href="http://codemacro.com">Kevin Lynx</a>
            &nbsp;posted at <a href="http://codemacro.com">http://codemacro.com</a>
            </p>

</div><img src ="http://www.cppblog.com/kevinlynx/aggbug/188786.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/kevinlynx/" target="_blank">Kevin Lynx</a> 2012-08-30 17:43 <a href="http://www.cppblog.com/kevinlynx/archive/2012/08/30/188786.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>使用memcmp比较两个变量结果一定吗？</title><link>http://www.cppblog.com/kevinlynx/archive/2012/08/17/187481.html</link><dc:creator>Kevin Lynx</dc:creator><author>Kevin Lynx</author><pubDate>Fri, 17 Aug 2012 06:07:00 GMT</pubDate><guid>http://www.cppblog.com/kevinlynx/archive/2012/08/17/187481.html</guid><wfw:comment>http://www.cppblog.com/kevinlynx/comments/187481.html</wfw:comment><comments>http://www.cppblog.com/kevinlynx/archive/2012/08/17/187481.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://www.cppblog.com/kevinlynx/comments/commentRss/187481.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/kevinlynx/services/trackbacks/187481.html</trackback:ping><description><![CDATA[<div class="entry-content">
<p>参考<a href="http://stackoverflow.com/questions/11994513/is-using-memcmp-on-array-of-int-strictly-conforming">Is using memcmp on array of int strictly conforming?</a></p>

<p>以下代码一定会输出ok吗？</p>

<div class="highlight">
<pre><code class="c"><span class="cp">#include &lt;stdio.h&gt;</span>
<span class="cp">#include &lt;string.h&gt;</span>

<span class="k">struct</span> <span class="n">S</span> <span class="p">{</span> <span class="kt">int</span> <span class="n">array</span><span class="p">[</span><span class="mi">2</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="k">struct</span> <span class="n">S</span> <span class="n">a</span> <span class="o">=</span> <span class="p">{</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="p">};</span>
    <span class="k">struct</span> <span class="n">S</span> <span class="n">b</span><span class="p">;</span>
    <span class="n">b</span> <span class="o">=</span> <span class="n">a</span><span class="p">;</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">memcmp</span><span class="p">(</span><span class="n">b</span><span class="p">.</span><span class="n">array</span><span class="p">,</span> <span class="n">a</span><span class="p">.</span><span class="n">array</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">b</span><span class="p">.</span><span class="n">array</span><span class="p">))</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">puts</span><span class="p">(</span><span class="s">"ok"</span><span class="p">);</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>




<!-- more -->


<p>我在vs2005以及gcc4.4.3上做了测试，都输出了ok。但这并不意味这个代码会永远输出ok。问题主要集中于这里使用了赋值语句来复制值，但却使用了memcmp这个基于内存数据比较的函数来比较值。</p>

<p>c语言中的赋值运算符（=）被定义为基于值的复制，而不是基于内存内容的复制。</p>

<blockquote><p><strong>C99 section 6.5.16.1 p2:</strong> In simple assignment (=), the value of the right operand is converted to the type of the assignment expression and replaces the value stored in the object designated by the left operand.</p></blockquote>

<p>这个其实很好理解，尤其在不同类型的数字类型间复制时，例如：</p>

<div class="highlight">
<pre><code class="c"><span class="kt">float</span> <span class="n">a</span> <span class="o">=</span> <span class="mf">1.1</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">b</span> <span class="o">=</span> <span class="n">a</span><span class="p">;</span>
</code></pre>
</div>


<p>因为浮点数和整形数的内存布局不一样，所以肯定是基于值的一种复制。另外，按照语言标准的思路来看，内存布局这种东西一般都属于实现相关的，所以语言标准是不会依赖实现去定义语言的。</p>

<p>上面的定理同样用于复杂数据类型，例如结构体。我们都知道结构体每个成员之间可能会有字节补齐，而使用赋值运算符来复制时，会不会复制这些补齐字节的内容，是语言标准未规定的。这意味着使用memcmp比较两个通过赋值运算符复制的两个结构体时，其结果是未定的。</p>

<p>但是上面的代码例子中，比较的其实是两个int数组。这也无法确认结果吗？这个问题最终集中于，难道int也会有不确定的补齐字节数据？</p>

<blockquote><p><strong>C99 6.2.6.2 integer types</strong> For signed integer types, the bits of the object representation shall be divided into three groups: value bits, padding bits, and the sign bit. [&#8230;] The values of any padding bits are unspecified.</p></blockquote>

<p>这话其实我也不太懂。一个有符号整数int，其内也有补齐二进制位(bits)？</p>

<p>但无论如何，这个例子都不算严谨的代码。人们的建议是使用memcpy来复制这种数据，因为memcpy和memcmp都是基于内存内容来工作的。</p>

<p class="post-footer">
            原文地址：
            <a href="http://codemacro.com/2012/08/17/memcmp-on-copy-value/">http://codemacro.com/2012/08/17/memcmp-on-copy-value/</a><br />
            written by <a href="http://codemacro.com">Kevin Lynx</a>
            &nbsp;posted at <a href="http://codemacro.com">http://codemacro.com</a>
            </p>

</div><img src ="http://www.cppblog.com/kevinlynx/aggbug/187481.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/kevinlynx/" target="_blank">Kevin Lynx</a> 2012-08-17 14:07 <a href="http://www.cppblog.com/kevinlynx/archive/2012/08/17/187481.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>像写函数式语言代码一样写C++</title><link>http://www.cppblog.com/kevinlynx/archive/2012/07/31/185720.html</link><dc:creator>Kevin Lynx</dc:creator><author>Kevin Lynx</author><pubDate>Tue, 31 Jul 2012 01:43:00 GMT</pubDate><guid>http://www.cppblog.com/kevinlynx/archive/2012/07/31/185720.html</guid><wfw:comment>http://www.cppblog.com/kevinlynx/comments/185720.html</wfw:comment><comments>http://www.cppblog.com/kevinlynx/archive/2012/07/31/185720.html#Feedback</comments><slash:comments>3</slash:comments><wfw:commentRss>http://www.cppblog.com/kevinlynx/comments/commentRss/185720.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/kevinlynx/services/trackbacks/185720.html</trackback:ping><description><![CDATA[<br /><p>忘记最早接触函数式编程语言是什么时候了，也忘记接触的第一门函数式语言是哪一门。断断续续接触过好几种函数式语言（当然都算不纯的，ruby/lisp不算纯吧），这些语言的思想在潜移默化中多多少少对我有所影响。</p>
<p>我是个C++程序员，我不知道我平时写的都是些什么代码。最让人印象深刻就是我会经常写遍历STL容器的代码，是经常，这样的遍历你可能也不陌生：</p>
<div class="highlight"><div style="background-color: #eeeeee; font-size: 13px; border: 1px solid #cccccc; padding: 4px 5px 4px 4px; width: 98%;"><!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--><span style="color: #0000ff;">for</span><span style="color: #000000;">&nbsp;(ListType::iterator&nbsp;it&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;con.begin();&nbsp;it&nbsp;</span><span style="color: #000000;">!=</span><span style="color: #000000;">&nbsp;con.end();&nbsp;</span><span style="color: #000000;">++</span><span style="color: #000000;">it)&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;something<br />}<br /></span></div>
</div>
<p>或者针对std::map/set等的查找：</p>
<div class="highlight"><div style="background-color: #eeeeee; font-size: 13px; border: 1px solid #cccccc; padding: 4px 5px 4px 4px; width: 98%;"><!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--><span style="color: #000000;">Table::iterator&nbsp;it&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;table.find(key);<br /></span><span style="color: #0000ff;">if</span><span style="color: #000000;">&nbsp;(it&nbsp;</span><span style="color: #000000;">==</span><span style="color: #000000;">&nbsp;table.end())<br />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">do</span><span style="color: #000000;">-</span><span style="color: #000000;">something<br /></span><span style="color: #0000ff;">do</span><span style="color: #000000;">-</span><span style="color: #000000;">something<br /></span></div>
</div>
<p>多亏STL接口的一致性，这让我们写出了很多&#8220;一致性&#8220;代码。慢慢地我觉得恶心，不禁想起函数式编程语言中，对于这种需求一般都会提供类似的接口：</p>
<div class="highlight"><div style="background-color: #eeeeee; font-size: 13px; border: 1px solid #cccccc; padding: 4px 5px 4px 4px; width: 98%;"><!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--><span style="color: #000000;">con.map(function&nbsp;(it)&nbsp;</span><span style="color: #0000ff;">if</span><span style="color: #000000;">&nbsp;(it</span><span style="color: #000000;">-&gt;</span><span style="color: #000000;">some</span><span style="color: #000000;">-</span><span style="color: #000000;">filed&nbsp;</span><span style="color: #000000;">==</span><span style="color: #000000;">&nbsp;some</span><span style="color: #000000;">-</span><span style="color: #000000;">value)&nbsp;</span><span style="color: #0000ff;">return</span><span style="color: #000000;">&nbsp;something&nbsp;end)<br />#&nbsp;或者<br />con.each&nbsp;</span><span style="color: #0000ff;">do</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">|</span><span style="color: #000000;">it</span><span style="color: #000000;">|</span><span style="color: #000000;">&nbsp;</span><span style="color: #0000ff;">if</span><span style="color: #000000;">&nbsp;it.some</span><span style="color: #000000;">-</span><span style="color: #000000;">filed&nbsp;</span><span style="color: #000000;">==</span><span style="color: #000000;">&nbsp;some</span><span style="color: #000000;">-</span><span style="color: #000000;">value&nbsp;then&nbsp;</span><span style="color: #0000ff;">return</span><span style="color: #000000;">&nbsp;something&nbsp;end&nbsp;end<br />#&nbsp;或者<br />(con.map&nbsp;(lambda&nbsp;(it)&nbsp;(</span><span style="color: #0000ff;">if</span><span style="color: #000000;">&nbsp;((</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;it.some</span><span style="color: #000000;">-</span><span style="color: #000000;">filed&nbsp;some</span><span style="color: #000000;">-</span><span style="color: #000000;">value))&nbsp;(</span><span style="color: #0000ff;">return</span><span style="color: #000000;">&nbsp;something))))<br /></span></div>
</div>
<p>（好吧，lisp我又忘了）总之，这种针对容器的遍历操作，都会成为一种内置接口，并且通过lambda来让用户直接编写处理代码，少去写循环的冗余。然后，我写了类似下面的一组宏（随手敲的不保证能运行）：</p>
<div class="highlight"><div style="background-color: #eeeeee; font-size: 13px; border: 1px solid #cccccc; padding: 4px 5px 4px 4px; width: 98%;"><!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--><span style="color: #0000ff;">#define</span><span style="color: #000000;">&nbsp;IT_N&nbsp;__it</span><span style="color: #000000;"><br /><br /></span><span style="color: #0000ff;">#define</span><span style="color: #000000;">&nbsp;TRAVERSE_MAP(type,&nbsp;map,&nbsp;exps)&nbsp;\</span><span style="color: #000000;"><br />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">for</span><span style="color: #000000;">&nbsp;(type::iterator&nbsp;IT_N&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;map.begin();&nbsp;IT_N&nbsp;</span><span style="color: #000000;">!=</span><span style="color: #000000;">&nbsp;map.end();&nbsp;</span><span style="color: #000000;">++</span><span style="color: #000000;">IT_N)&nbsp;{&nbsp;\<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;exps;&nbsp;\<br />&nbsp;&nbsp;&nbsp;&nbsp;}<br /></span><span style="color: #0000ff;">#define</span><span style="color: #000000;">&nbsp;I_KEY&nbsp;(IT_N-&gt;first)</span><span style="color: #000000;"><br /></span><span style="color: #0000ff;">#define</span><span style="color: #000000;">&nbsp;I_VALUE&nbsp;(IT_N-&gt;second)</span><span style="color: #000000;"><br /><br /></span><span style="color: #0000ff;">#define</span><span style="color: #000000;">&nbsp;TRAVERSE_LIST(type,&nbsp;list,&nbsp;exps)&nbsp;\</span><span style="color: #000000;"><br />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">for</span><span style="color: #000000;">&nbsp;(type::iterator&nbsp;IT_N&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;list.begin();&nbsp;IT_N&nbsp;</span><span style="color: #000000;">!=</span><span style="color: #000000;">&nbsp;list.end();&nbsp;</span><span style="color: #000000;">++</span><span style="color: #000000;">IT_N)&nbsp;{&nbsp;\<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;exps;&nbsp;\<br />&nbsp;&nbsp;&nbsp;&nbsp;}<br /></span><span style="color: #0000ff;">#define</span><span style="color: #000000;">&nbsp;L_VALUE&nbsp;(*IT_N)</span><span style="color: #000000;"><br /><br /></span><span style="color: #0000ff;">#define</span><span style="color: #000000;">&nbsp;FIND_MAP_ITEM(type,&nbsp;map,&nbsp;key,&nbsp;fexps,&nbsp;texps)&nbsp;\</span><span style="color: #000000;"><br />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">do</span><span style="color: #000000;">&nbsp;{&nbsp;\<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;type::iterator&nbsp;IT_N&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;map.find(key);&nbsp;\<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">if</span><span style="color: #000000;">&nbsp;(IT_N&nbsp;</span><span style="color: #000000;">==</span><span style="color: #000000;">&nbsp;map.end())&nbsp;{&nbsp;\<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fexps;&nbsp;\<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;</span><span style="color: #0000ff;">else</span><span style="color: #000000;">&nbsp;{&nbsp;\<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;texps;&nbsp;\<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;\<br />&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;</span><span style="color: #0000ff;">while</span><span style="color: #000000;">(</span><span style="color: #000000;">0</span><span style="color: #000000;">)<br /><br /></span><span style="color: #0000ff;">#define</span><span style="color: #000000;">&nbsp;VAL_N&nbsp;__val</span><span style="color: #000000;"><br /></span><span style="color: #0000ff;">#define</span><span style="color: #000000;">&nbsp;FIND_LIST_ITEM_IF(type,&nbsp;list,&nbsp;cmp,&nbsp;fexps,&nbsp;texps)&nbsp;\</span><span style="color: #000000;"><br />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">do</span><span style="color: #000000;">&nbsp;{&nbsp;\<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">struct</span><span style="color: #000000;">&nbsp;Comp&nbsp;{&nbsp;\<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">bool</span><span style="color: #000000;">&nbsp;</span><span style="color: #0000ff;">operator</span><span style="color: #000000;">()&nbsp;(</span><span style="color: #0000ff;">const</span><span style="color: #000000;">&nbsp;type::value_type&nbsp;</span><span style="color: #000000;">&amp;</span><span style="color: #000000;">VAL_N)&nbsp;</span><span style="color: #0000ff;">const</span><span style="color: #000000;">&nbsp;{&nbsp;\<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">return</span><span style="color: #000000;">&nbsp;cmp;&nbsp;\<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;\<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;};&nbsp;\<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;type::iterator&nbsp;IT_N&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;std::find_if(list.begin(),&nbsp;list.end(),&nbsp;Comp());&nbsp;\<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">if</span><span style="color: #000000;">&nbsp;(IT_N&nbsp;</span><span style="color: #000000;">!=</span><span style="color: #000000;">&nbsp;list.end())&nbsp;{&nbsp;\<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;texps;&nbsp;\<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;</span><span style="color: #0000ff;">else</span><span style="color: #000000;">&nbsp;{&nbsp;\<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fexps;&nbsp;\<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;\<br />&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;</span><span style="color: #0000ff;">while</span><span style="color: #000000;">(</span><span style="color: #000000;">0</span><span style="color: #000000;">)<br /><br /></span><span style="color: #0000ff;">#define</span><span style="color: #000000;">&nbsp;NULL_EXP&nbsp;;</span><span style="color: #000000;"><br /></span></div>
</div>
<p>当然，以上接口都还包含一些const版本，用于const容器的使用。使用的时候（截取的项目中的使用例子）：</p>
<div class="highlight"><div style="background-color: #eeeeee; font-size: 13px; border: 1px solid #cccccc; padding: 4px 5px 4px 4px; width: 98%;"><!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--><span style="color: #000000;">TRAVERSE_MAP(TimerTable,&nbsp;m_timers,&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;I_VALUE.obj</span><span style="color: #000000;">-&gt;</span><span style="color: #000000;">OnTimerCancel(I_KEY,&nbsp;I_VALUE.arg);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;TIMER_CANCEL(I_VALUE.id));&nbsp;<br /><br />TRAVERSE_LIST(AreaList,&nbsp;areas,<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ids.push_back(L_VALUE</span><span style="color: #000000;">-&gt;</span><span style="color: #000000;">ID()));<br /><br />FIND_MAP_ITEM(PropertyTable,&nbsp;m_properties,&nbsp;name,<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;LogWarn(</span><span style="color: #000000;">"</span><span style="color: #000000;">set&nbsp;a&nbsp;non-existed&nbsp;property&nbsp;%s</span><span style="color: #000000;">"</span><span style="color: #000000;">,&nbsp;name.c_str());&nbsp;</span><span style="color: #0000ff;">return</span><span style="color: #000000;">&nbsp;NIL_VALUE,<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">if</span><span style="color: #000000;">&nbsp;(val.Type()&nbsp;</span><span style="color: #000000;">!=</span><span style="color: #000000;">&nbsp;I_VALUE.type())&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">return</span><span style="color: #000000;">&nbsp;NIL_VALUE;&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;</span><span style="color: #0000ff;">else</span><span style="color: #000000;">&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;GValue&nbsp;old&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;I_VALUE;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;I_VALUE&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;val;&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">return</span><span style="color: #000000;">&nbsp;old;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;});<br /><br /></span></div>
</div>
<p>多亏了C/C++宏对一切内容的可容纳性，可以让我往宏参数里塞进像if这种复合语句，甚至多条语句（例如最后一个例子）。这些宏我使用了一段时间，开始觉得挺爽，很多函数的实现里，我再也不用写那些重复的代码了。但是后来我发觉这些代码越来越恶心了。最大的弊端在于不可调试，我只能将断点下到更深的代码层；然后就是看起来特不直观，连作者自己都看得觉得不直观了，可想而知那些连函数式编程语言都不知道是什么的C++程序员看到这些代码会是什么心情（可以想象哥已经被诅咒了多少次）。</p>
<p>函数式语言让人写出更短的代码，这一点也对我有影响，例如我最近又写下了一些邪恶代码：</p>
<div class="highlight"><div style="background-color: #eeeeee; font-size: 13px; border: 1px solid #cccccc; padding: 4px 5px 4px 4px; width: 98%;"><!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--><span style="color: #008000;">//</span><span style="color: #008000;">&nbsp;split&nbsp;a&nbsp;string&nbsp;into&nbsp;several&nbsp;sub&nbsp;strings&nbsp;by&nbsp;a&nbsp;split&nbsp;character&nbsp;i.e:<br /></span><span style="color: #008000;">//</span><span style="color: #008000;">&nbsp;"a;b;c;"&nbsp;=&gt;&nbsp;"a",&nbsp;"b",&nbsp;"c"<br /></span><span style="color: #008000;">//</span><span style="color: #008000;">&nbsp;"a;b;c"&nbsp;=&gt;&nbsp;"a",&nbsp;"b",&nbsp;"c"</span><span style="color: #008000;"><br /></span><span style="color: #000000;">std::vector</span><span style="color: #000000;">&lt;</span><span style="color: #000000;">std::</span><span style="color: #0000ff;">string</span><span style="color: #000000;">&gt;</span><span style="color: #000000;">&nbsp;SplitString(</span><span style="color: #0000ff;">const</span><span style="color: #000000;">&nbsp;std::</span><span style="color: #0000ff;">string</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">&amp;</span><span style="color: #000000;">str,&nbsp;</span><span style="color: #0000ff;">char</span><span style="color: #000000;">&nbsp;split)&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;std::vector</span><span style="color: #000000;">&lt;</span><span style="color: #000000;">std::</span><span style="color: #0000ff;">string</span><span style="color: #000000;">&gt;</span><span style="color: #000000;">&nbsp;ret;<br />&nbsp;&nbsp;&nbsp;&nbsp;size_t&nbsp;last&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">0</span><span style="color: #000000;">;<br />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">for</span><span style="color: #000000;">&nbsp;(size_t&nbsp;pos&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;str.find(split);&nbsp;pos&nbsp;</span><span style="color: #000000;">!=</span><span style="color: #000000;">&nbsp;std::</span><span style="color: #0000ff;">string</span><span style="color: #000000;">::npos;&nbsp;last&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;pos&nbsp;</span><span style="color: #000000;">+</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">1</span><span style="color: #000000;">,&nbsp;pos&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;str.find(split,&nbsp;last))&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ret.push_back(str.substr(last,&nbsp;pos&nbsp;</span><span style="color: #000000;">-</span><span style="color: #000000;">&nbsp;last));<br />&nbsp;&nbsp;&nbsp;&nbsp;}<br />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">return</span><span style="color: #000000;">&nbsp;last&nbsp;</span><span style="color: #000000;">&lt;</span><span style="color: #000000;">&nbsp;str.length()&nbsp;</span><span style="color: #000000;">?</span><span style="color: #000000;">&nbsp;ret.push_back(str.substr(last))&nbsp;:&nbsp;</span><span style="color: #000000;">0</span><span style="color: #000000;">,&nbsp;ret;<br />}<br /></span></div>
</div>
<p>恶心的就是最后那条return语句，因为我需要处理&#8221;a;b;c&#8221;这种c后面没加分隔符的情况，但我并不愿意为了这个需求再写一个会占超过一行的if语句。因为，我太喜欢ruby里的if了：</p>
<br /><div style="background-color: #eeeeee; font-size: 13px; border: 1px solid #cccccc; padding: 4px 5px 4px 4px; width: 98%;"><!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--><span style="color: #0000ff;">do</span><span style="color: #000000;">-</span><span style="color: #000000;">something&nbsp;</span><span style="color: #0000ff;">if</span><span style="color: #000000;">&nbsp;exp<br /></span></div>
<p>也就是ruby里允许这种只有一行if的代码将if放在其后并作为一条语句。我的不愿意其实是有理由的，在c/c++中有太多只有一行条件体的if语句，对这些语句参合进编程风格/可读性进来后，就不得不让你写出不安的代码，例如：</p>
<div class="highlight"><div style="background-color: #eeeeee; font-size: 13px; border: 1px solid #cccccc; padding: 4px 5px 4px 4px; width: 98%;"><!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--><span style="color: #0000ff;">if</span><span style="color: #000000;">&nbsp;(something)&nbsp;</span><span style="color: #0000ff;">return</span><span style="color: #000000;">&nbsp;something;&nbsp;</span><span style="color: #008000;">//</span><span style="color: #008000;">&nbsp;某些编程风格里不允许这样做，因为它不方便调试</span><span style="color: #008000;"><br /></span><span style="color: #000000;"><br /></span><span style="color: #0000ff;">if</span><span style="color: #000000;">&nbsp;(something)&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">return</span><span style="color: #000000;">&nbsp;something;&nbsp;</span><span style="color: #008000;">//</span><span style="color: #008000;">&nbsp;某些风格里又有大括号的统一要求</span><span style="color: #008000;"><br /></span><span style="color: #000000;"><br /></span><span style="color: #0000ff;">if</span><span style="color: #000000;">&nbsp;(something)&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">return</span><span style="color: #000000;">&nbsp;something;&nbsp;</span><span style="color: #008000;">//</span><span style="color: #008000;">&nbsp;就算符合风格了，但这一条语句就得多个大括号</span><span style="color: #008000;"><br /></span><span style="color: #000000;">}<br /><br /></span><span style="color: #0000ff;">if</span><span style="color: #000000;">&nbsp;(something)&nbsp;<br />{<br />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">return</span><span style="color: #000000;">&nbsp;something;&nbsp;</span><span style="color: #008000;">//</span><span style="color: #008000;">&nbsp;某些风格里这大括号就更奢侈了</span><span style="color: #008000;"><br /></span><span style="color: #000000;">}<br /></span></div>
</div>
<p>这个return除了乍看上去有点纠结外，其实也不算什么大问题，但是那个问号表达式返回的0实在没有任何意义，而正是没有意义才会让它误导人。本来我是可以写成：</p>
<div class="highlight"><div style="background-color: #eeeeee; font-size: 13px; border: 1px solid #cccccc; padding: 4px 5px 4px 4px; width: 98%;"><!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--><span style="color: #0000ff;">return</span><span style="color: #000000;">&nbsp;last&nbsp;</span><span style="color: #000000;">&lt;</span><span style="color: #000000;">&nbsp;str.length()&nbsp;</span><span style="color: #000000;">&amp;&amp;</span><span style="color: #000000;">&nbsp;ret.push_back(str.substr(last)),&nbsp;ret;<br /></span></div>
</div>
<p>这样利用条件表达式的短路运算，代码也清晰多了。但是，std::vector::push_back是一个没有返回值的函数，所以。</p>
<p>全文完。</p>
<p class="post-footer">
 原文地址：
 <a href="http://codemacro.com/2012/07/30/write-cpp-like-fp/">http://codemacro.com/2012/07/30/write-cpp-like-fp/</a><br />
 written by <a href="http://codemacro.com">Kevin Lynx</a>
 &nbsp;posted at <a href="http://codemacro.com">http://codemacro.com</a>
 </p><img src ="http://www.cppblog.com/kevinlynx/aggbug/185720.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/kevinlynx/" target="_blank">Kevin Lynx</a> 2012-07-31 09:43 <a href="http://www.cppblog.com/kevinlynx/archive/2012/07/31/185720.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>tolua的tolua_toxxx系列API设计</title><link>http://www.cppblog.com/kevinlynx/archive/2012/05/10/174460.html</link><dc:creator>Kevin Lynx</dc:creator><author>Kevin Lynx</author><pubDate>Thu, 10 May 2012 07:38:00 GMT</pubDate><guid>http://www.cppblog.com/kevinlynx/archive/2012/05/10/174460.html</guid><wfw:comment>http://www.cppblog.com/kevinlynx/comments/174460.html</wfw:comment><comments>http://www.cppblog.com/kevinlynx/archive/2012/05/10/174460.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/kevinlynx/comments/commentRss/174460.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/kevinlynx/services/trackbacks/174460.html</trackback:ping><description><![CDATA[<p>原文链接：<a href="http://codemacro.com/2012/05/10/tolua-api/">http://codemacro.com/2012/05/10/tolua-api/
</a></p>
<p>我们使用tolua++手工绑定c/c++接口到lua中，在绑定的接口实现里，就需要取出传入的参数。tolua++中提供了一系列tolua_toxxx函数，例如：</p>
<div class="highlight"><pre><code class="c"><span class="n">lua_Number</span> <span class="n">tolua_tonumber</span><span class="p">(</span><span class="n">lua_State</span> <span class="o">*</span><span class="n">L</span><span class="p">,</span> <span class="kt">int</span> <span class="n">narg</span><span class="p">,</span> <span class="n">lua_Number</span> <span class="n">def</span><span class="p">)</span>
<span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">tolua_tostring</span><span class="p">(</span><span class="n">lua_State</span> <span class="o">*</span><span class="n">L</span><span class="p">,</span> <span class="kt">int</span> <span class="n">narg</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">def</span><span class="p">)</span>
</code></pre>
</div>
<p>这些函数都有一个def参数。乍一看，这些函数使用起来很简单。传入lua_State，传入参数在栈中的位置，然后再传一个<strong>失败</strong>后返回的默认值。</p>
<p>我重点要说的是这里这个<strong>失败</strong>，按正常程序员的理解，针对lua而言，什么情况下算失败呢？lua语言里函数参数支持不传，此时实参为nil，将nil转换为一个c类型必然失败；参数类型不正确算不算失败？你传一个user data，c里按数字来取，这也算失败。</p>
<!-- more -->
<p>这么简单的API还需要多纠结什么呢？然后我们浩浩荡荡地写了上百个接口，什么tolua_tostring/tolua_tonumber的使用少说也有500了吧？</p>
<p>然后有一天，服务器宕机了，空指针:</p>
<div class="highlight"><pre><code class="c"><span class="cm">/* 失败返回""，还能省空指针的判断 */</span>
<span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">name</span> <span class="o">=</span> <span class="n">tolua_tostring</span><span class="p">(</span><span class="n">L</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="s">""</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">name</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">==</span> <span class="sc">'\0'</span><span class="p">)</span> <span class="p">{</span> <span class="cm">/* 空串总得判断吧 */</span>
 <span class="p">...</span>
<span class="p">}</span>
</code></pre>
</div>
<p>跟踪后发现，脚本里传入的是nil，这里的name取出来是NULL，而不是&#8221;&#8220;（的地址）。然后吐槽了一下这个API，辛苦地修改了所有类似代码，增加对空指针的判断。我没有多想。</p>
<p>故事继续，有一天服务器虽然没宕机，但功能不正常了:</p>
<div class="highlight"><pre><code class="c"><span class="kt">float</span> <span class="n">angle</span> <span class="o">=</span> <span class="p">(</span><span class="kt">float</span><span class="p">)</span> <span class="n">tolua_tonumber</span><span class="p">(</span><span class="n">L</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span> <span class="o">*</span> <span class="n">PI</span><span class="p">);</span>
<span class="p">...</span>
</code></pre>
</div>
<p>这个意思是，这个函数的参数1默认是2*PI，什么是默认？lua里某函数参数不传，或传nil就是使用默认。因为不传的话，这个实参本身就是nil。但，tolua_tonumber的行为不是这样的，它的实现真是偷懒:</p>
<div class="highlight"><pre><code class="c"><span class="n">TOLUA_API</span> <span class="n">lua_Number</span> <span class="nf">tolua_tonumber</span> <span class="p">(</span><span class="n">lua_State</span><span class="o">*</span> <span class="n">L</span><span class="p">,</span> <span class="kt">int</span> <span class="n">narg</span><span class="p">,</span> <span class="n">lua_Number</span> <span class="n">def</span><span class="p">)</span>
<span class="p">{</span>
 <span class="k">return</span> <span class="n">lua_gettop</span><span class="p">(</span><span class="n">L</span><span class="p">)</span><span class="o">&lt;</span><span class="n">abs</span><span class="p">(</span><span class="n">narg</span><span class="p">)</span> <span class="o">?</span> <span class="n">def</span> <span class="o">:</span> <span class="n">lua_tonumber</span><span class="p">(</span><span class="n">L</span><span class="p">,</span><span class="n">narg</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">TOLUA_API</span> <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="nf">tolua_tostring</span> <span class="p">(</span><span class="n">lua_State</span><span class="o">*</span> <span class="n">L</span><span class="p">,</span> <span class="kt">int</span> <span class="n">narg</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">def</span><span class="p">)</span>
<span class="p">{</span>
 <span class="k">return</span> <span class="n">lua_gettop</span><span class="p">(</span><span class="n">L</span><span class="p">)</span><span class="o">&lt;</span><span class="n">abs</span><span class="p">(</span><span class="n">narg</span><span class="p">)</span> <span class="o">?</span> <span class="n">def</span> <span class="o">:</span> <span class="n">lua_tostring</span><span class="p">(</span><span class="n">L</span><span class="p">,</span><span class="n">narg</span><span class="p">);</span>
<span class="p">}</span>
</code></pre>
</div>
<p>意思是，只有当你不传的时候，它才返回默认值，否则就交给lua的API来管，而lua这些API是不支持应用层的默认参数的，对于lua_tonumber错误时就返回0，lua_tostring错误时就返回NULL。</p>
<p>这种其行为和其带来的common sense不一致的API设计，实在让人蛋疼。什么是common sense呢？就像一个UI库里的按钮，我们都知道有click事件，hover事件，UI库的文档甚至都不需要解释什么是click什么是hover，因为大家看到这个东西，就有了共识，无需废话，这就是common sense。就像tolua的这些API，非常普通，大家一看都期待在意外情况下你能返回def值。但它竟然不是。实在不行，你可以模仿lua的check系列函数的实现嘛:</p>
<div class="highlight"><pre><code class="c"><span class="n">LUALIB_API</span> <span class="n">lua_Number</span> <span class="nf">luaL_checknumber</span> <span class="p">(</span><span class="n">lua_State</span> <span class="o">*</span><span class="n">L</span><span class="p">,</span> <span class="kt">int</span> <span class="n">narg</span><span class="p">)</span> <span class="p">{</span>
 <span class="n">lua_Number</span> <span class="n">d</span> <span class="o">=</span> <span class="n">lua_tonumber</span><span class="p">(</span><span class="n">L</span><span class="p">,</span> <span class="n">narg</span><span class="p">);</span>
 <span class="k">if</span> <span class="p">(</span><span class="n">d</span> <span class="o">==</span> <span class="mi">0</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">lua_isnumber</span><span class="p">(</span><span class="n">L</span><span class="p">,</span> <span class="n">narg</span><span class="p">))</span> <span class="cm">/* avoid extra test when d is not 0 */</span>
 <span class="n">tag_error</span><span class="p">(</span><span class="n">L</span><span class="p">,</span> <span class="n">narg</span><span class="p">,</span> <span class="n">LUA_TNUMBER</span><span class="p">);</span>
 <span class="k">return</span> <span class="n">d</span><span class="p">;</span>
<span class="p">}</span>
</code></pre>
</div>
<p>即，根本不用去检查栈问题，直接在lua_tonumber之后再做包装检查。何况，lua需要你去检查栈吗？当你访问了栈外的元素时，lua会自动返回一个全局常量luaO_nilobject:</p>
<div class="highlight"><pre><code class="c"><span class="k">static</span> <span class="n">TValue</span> <span class="o">*</span><span class="nf">index2adr</span><span class="p">(</span><span class="n">lua_State</span> <span class="o">*</span><span class="n">L</span><span class="p">,</span> <span class="kt">int</span> <span class="n">idx</span><span class="p">)</span> <span class="p">{</span>
 <span class="p">...</span>
 <span class="k">if</span> <span class="p">(</span><span class="n">o</span> <span class="o">&gt;=</span> <span class="n">L</span><span class="o">-&gt;</span><span class="n">top</span><span class="p">)</span> <span class="k">return</span> <span class="n">cast</span><span class="p">(</span><span class="n">TValue</span><span class="o">*</span><span class="p">,</span> <span class="n">luaO_nilobject</span><span class="p">);</span>
<span class="p">}</span>
</code></pre>
</div>
<p>另，程序悲剧也来源于臆想。</p><img src ="http://www.cppblog.com/kevinlynx/aggbug/174460.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/kevinlynx/" target="_blank">Kevin Lynx</a> 2012-05-10 15:38 <a href="http://www.cppblog.com/kevinlynx/archive/2012/05/10/174460.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>多重继承和void*的糗事</title><link>http://www.cppblog.com/kevinlynx/archive/2011/04/30/145409.html</link><dc:creator>Kevin Lynx</dc:creator><author>Kevin Lynx</author><pubDate>Sat, 30 Apr 2011 12:14:00 GMT</pubDate><guid>http://www.cppblog.com/kevinlynx/archive/2011/04/30/145409.html</guid><wfw:comment>http://www.cppblog.com/kevinlynx/comments/145409.html</wfw:comment><comments>http://www.cppblog.com/kevinlynx/archive/2011/04/30/145409.html#Feedback</comments><slash:comments>12</slash:comments><wfw:commentRss>http://www.cppblog.com/kevinlynx/comments/commentRss/145409.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/kevinlynx/services/trackbacks/145409.html</trackback:ping><description><![CDATA[<div id="void" class="document"><h1 class="title">&#x591A;&#x91CD;&#x7EE7;&#x627F;&#x548C;void*&#x7684;&#x7CD7;&#x4E8B;</h1><table rules="none" frame="void" class="docinfo"><col class="docinfo-name"></col><col class="docinfo-content"></col><tbody valign="top"><tr><th class="docinfo-name">Author:</th><td>Kevin Lynx</td></tr><tr><th class="docinfo-name">Date:</th><td>4.30.2011</td></tr></tbody></table><p>C++&#x4E3A;&#x4E86;&#x517C;&#x5BB9;C&#xFF0C;&#x5BFC;&#x81F4;&#x4E86;&#x4E0D;&#x5C11;&#x8BED;&#x8A00;&#x9634;&#x6697;&#x9762;&#x3002;Bjarne Stroustrup&#x5728;&lt;D&amp;E&gt;&#x4E00;&#x4E66;&#x91CC;&#x4E5F;&#x5E38;&#x4E3A;&#x6B64;&#x8868;&#x73B0;&#x51FA;&#x65E0;&#x5948;&#x3002;&#x53E6;&#x4E00;&#x65B9;&#x9762;&#xFF0C;&#x5F3A;&#x5236;&#x8F6C;&#x6362;&#x4E5F;&#x662F;C++&#x7684;&#x4E00;&#x5927;&#x8BDF;&#x75C5;&#x3002;&#x4F46;&#x662F;&#xFF0C;&#x56E0;&#x4E3A;&#x6211;&#x4EEC;&#x7684;&#x5E94;&#x7528;&#x73AF;&#x5883;&#x603B;&#x662F;&#x90A3;&#x4E48;&#x201C;&#x4E0D;
&#x7EAF;&#x201D;&#xFF0C;&#x6240;&#x4EE5;&#x4E5F;&#x5C31;&#x5E38;&#x5E38;&#x5BFC;&#x81F4;&#x5404;&#x79CD;&#x95EE;&#x9898;&#x3002;</p><p>&#x672C;&#x6587;&#x5373;&#x63CF;&#x8FF0;&#x4E86;&#x4E00;&#x4E2A;&#x5173;&#x4E8E;&#x5F3A;&#x5236;&#x8F6C;&#x6362;&#x5E26;&#x6765;&#x7684;&#x95EE;&#x9898;&#x3002;&#x8FD9;&#x4E2A;&#x95EE;&#x9898;&#x51E0;&#x5E74;&#x524D;&#x6211;&#x66FE;&#x9047;&#x5230;&#x8FC7;(&lt;<a href="http://www.cppblog.com/kevinlynx/archive/2008/04/24/48001.html" class="reference external">&#x591A;&#x7EBF;&#x7A0B;&#x4E0B;vc2003,vc2005&#x5BF9;&#x865A;&#x51FD;&#x6570;&#x8868;&#x5904;&#x7406;&#x7684;BUG&#xFF1F;</a>&gt;)&#xFF0C;&#x5F53;&#x65F6;&#x6CA1;&#x6765;&#x5F97;&#x53CA;&#x6DF1;&#x7A76;&#x3002;&#x6DF1;&#x7A76;C++&#x7684;&#x67D0;&#x4E9B;&#x8BED;&#x6CD5;&#xFF0C;&#x5B9E;&#x5728;&#x662F;&#x4EF6;&#x8F9B;&#x82E6;&#x4E8B;&#x3002;&#x6240;&#x4EE5;&#xFF0C;&#x8FD9;&#x91CC;&#x4E5F;&#x4E0D;&#x63D0;&#x8FC7;&#x4E8E;&#x8BE1;&#x5F02;&#x7684;&#x7528;&#x6CD5;&#x3002;</p><div id="id1" class="section"><h1>&#x95EE;&#x9898;</h1><p>&#x8003;&#x8651;&#x4E0B;&#x9762;&#x975E;&#x5E38;&#x666E;&#x901A;&#x7684;&#x591A;&#x91CD;&#x7EE7;&#x627F;&#x4EE3;&#x7801;:</p><pre class="literal-block">
class Left {
public:
    virtual void ldisplay () {
        printf (&quot;Left::ldisplay\n&quot;);
    }
};

class Right {
public:
    virtual void rdisplay () {
        printf (&quot;Right::rdisplay\n&quot;);
    }
};

class Bottom : public Left, public Right {
public:
    virtual void ldisplay () {
        printf (&quot;Bottom::ldisplay\n&quot;);
    }
};
</pre><p>&#x8FD9;&#x6837;&#x5B50;&#x7684;&#x4EE3;&#x7801;&#x5728;&#x6211;&#x4EEC;&#x7684;&#x9879;&#x76EE;&#x4E2D;&#x5F88;&#x5BB9;&#x6613;&#x5C31;&#x4F1A;&#x51FA;&#x73B0;&#xFF0C;&#x4F8B;&#x5982;:</p><pre class="literal-block">
class BaseObject;
class EventListener;
class Player : public BaseObject, public EventListener
</pre><p>&#x522B;&#x7D27;&#x5F20;&#xFF0C;&#x6211;&#x5F53;&#x7136;&#x4E0D;&#x4F1A;&#x544A;&#x8BC9;&#x4F60;&#x8FD9;&#x6837;&#x7684;&#x4EE3;&#x7801;&#x662F;&#x6709;&#x5B89;&#x5168;&#x9690;&#x60A3;&#x7684;&#x3002;&#x4F46;&#x5B83;&#x4EEC;&#x786E;&#x5B9E;&#x5728;&#x67D0;&#x4E9B;&#x65F6;&#x5019;&#x4F1A;&#x51FA;&#x73B0;&#x9690;&#x60A3;&#x3002;&#x5728;&#x6211;&#x4EEC;&#x7684;C++&#x9879;&#x76EE;&#x4E2D;&#xFF0C;&#x4E5F;&#x6781;&#x6709;&#x53EF;&#x80FD;&#x4F1A;&#x4E0E;&#x4E00;&#x4E9B;&#x7EAF;C&#x6A21;&#x5757;&#x6253;&#x4EA4;&#x9053;&#x3002;&#x5728;C&#x8BED;&#x8A00;&#x91CC;&#xFF0C;&#x6781;&#x6709;&#x80AF;&#x80FD;&#x51FA;&#x73B0;&#x4EE5;
&#x4E0B;&#x7684;&#x4EE3;&#x7801;:</p><pre class="literal-block">
typedef void (*allocator) (void *u);
void set_allocator (allocator alloc, void *u);
</pre><p>&#x4E4B;&#x6240;&#x4EE5;&#x4F7F;&#x7528;&#x56DE;&#x8C03;&#x51FD;&#x6570;&#xFF0C;&#x662F;&#x51FA;&#x4E8E;&#x5BF9;&#x6A21;&#x5757;&#x7684;&#x901A;&#x7528;&#x6027;&#x7684;&#x8003;&#x8651;&#x3002;&#x800C;&#x5728;&#x8C03;&#x7528;&#x56DE;&#x8C03;&#x51FD;&#x6570;&#x65F6;&#xFF0C;&#x4E5F;&#x901A;&#x5E38;&#x4F1A;&#x9884;&#x7559;&#x4E00;&#x4E2A;user data&#x7684;&#x6307;&#x9488;&#xFF0C;&#x7528;&#x4E8E;&#x8BA9;&#x5E94;&#x7528;&#x5C42;&#x81EA;&#x7531;&#x5730;&#x4F20;&#x9012;&#x6570;&#x636E;&#x3002;</p><p>&#x4EE5;&#x4E0A;&#x5173;&#x4E8E;&#x591A;&#x91CD;&#x7EE7;&#x627F;&#x548C;void*&#x7684;&#x4F7F;&#x7528;&#x4E2D;&#xFF0C;&#x90FD;&#x5C5E;&#x4E8E;&#x5F88;&#x5E38;&#x89C4;&#x7684;&#x7528;&#x6CD5;&#x3002;&#x4F46;&#x662F;&#x5F53;&#x5B83;&#x4EEC;&#x9047;&#x5230;&#x4E00;&#x8D77;&#x65F6;&#xFF0C;&#x4E8B;&#x60C5;&#x5C31;&#x60B2;&#x5267;&#x4E86;&#x3002;&#x8003;&#x8651;&#x4E0B;&#x9762;&#x7684;&#x4EE3;&#x7801;:</p><pre class="literal-block">
Bottom *bobj = new Bottom(); // we HAVE a bottom object
Right *robj = bobj; // robj point to bobj?
robj-&gt;rdisplay(); // display what ?
void *vobj = bobj; // we have a VOID* pointer
robj = (Right*) vobj; // convert it back
robj-&gt;rdisplay(); // display what?
</pre><p>&#x8FD9;&#x91CC;&#x7684;&#x8F93;&#x51FA;&#x7ED3;&#x679C;&#x662F;&#x4EC0;&#x4E48;&#x5462;&#xFF1F;:</p><pre class="literal-block">
Right::rdisplay
Bottom::ldisplay // !!!!
</pre><p>&#x7531;void*&#x8F6C;&#x56DE;&#x6765;&#x7684;robj&#x8C03;&#x7528;rdisplay&#x65F6;&#xFF0C;&#x5374;&#x8C03;&#x7528;&#x4E86;&#x83AB;&#x540D;&#x5176;&#x5999;&#x7684;Bottom::ldisplay&#xFF01;</p></div><div id="id2" class="section"><h1>&#x591A;&#x91CD;&#x7EE7;&#x627F;&#x7C7B;&#x7684;&#x5185;&#x5B58;&#x5E03;&#x5C40;</h1><p>&#x7C7B;&#x5BF9;&#x8C61;&#x7684;&#x5185;&#x5B58;&#x5E03;&#x5C40;&#xFF0C;&#x5E76;&#x4E0D;&#x5C5E;&#x4E8E;C++&#x6807;&#x51C6;&#x3002;&#x8FD9;&#x91CC;&#x4EC5;&#x4EE5;vs2005&#x4E3A;&#x4F8B;&#x3002;&#x4E0A;&#x9762;&#x4F8B;&#x5B50;&#x4E2D;&#xFF0C;Bottom&#x7C7B;&#x7684;&#x5185;&#x5B58;&#x5E03;&#x5C40;&#x5927;&#x6982;&#x5982;&#x4E0B;:</p><pre class="literal-block">
+-------------+
| Left_vptr   |
+-------------+
| Left data   |
+-------------+
| Right_vptr  |
+-------------+
| Right data  |
+-------------+
| Bottom data |
+-------------+
</pre><p>&#x4E0E;&#x5355;&#x7EE7;&#x627F;&#x4E0D;&#x540C;&#x7684;&#x662F;&#xFF0C;&#x591A;&#x91CD;&#x7EE7;&#x627F;&#x7684;&#x7C7B;&#x91CC;&#xFF0C;&#x53EF;&#x80FD;&#x4F1A;&#x5305;&#x542B;&#x591A;&#x4E2A;vptr&#x3002;&#x5F53;&#x4E00;&#x4E2A;Bottom&#x5BF9;&#x8C61;&#x88AB;&#x6784;&#x9020;&#x597D;&#x65F6;&#xFF0C;&#x5176;&#x5185;&#x90E8;&#x7684;&#x4E24;&#x4E2A;vptr&#x4E5F;&#x88AB;&#x6B63;&#x786E;&#x521D;&#x59CB;&#x5316;&#xFF0C;&#x5176;&#x6307;&#x5411;&#x7684;vtable&#x5206;&#x522B;&#x4E3A;:</p><pre class="literal-block">
Left_vptr ---&gt;  +---------------------+
                | 0: Bottom::ldisplay |
                +---------------------+

Right_vptr ---&gt; +---------------------+
                | 0: Right::rdisplay  |
                +---------------------+
</pre></div><div id="id3" class="section"><h1>&#x8F6C;&#x6362;&#x7684;&#x5185;&#x5E55;</h1><p><strong>&#x7C7B;&#x4F53;&#x7CFB;&#x95F4;&#x7684;&#x8F6C;&#x6362;</strong></p><p>&#x9690;&#x5F0F;&#x8F6C;&#x6362;&#x76F8;&#x6BD4;&#x5F3A;&#x5236;&#x8F6C;&#x6362;&#x800C;&#x8A00;&#xFF0C;&#x4E00;&#x5B9A;&#x7B97;&#x662F;&#x4F18;&#x7F8E;&#x7684;&#x4EE3;&#x7801;&#x3002;&#x8003;&#x8651;&#x5982;&#x4E0B;&#x4EE3;&#x7801;&#x7684;&#x8F93;&#x51FA;:</p><pre class="literal-block">
Bottom *bobj = new Bottom();
printf (&quot;%p\n&quot;, bobj);
Right *robj = bobj;
printf (&quot;%p\n&quot;, robj);
</pre><p>&#x5176;&#x8F93;&#x51FA;&#x7ED3;&#x679C;&#x53EF;&#x80FD;&#x4E3A;:</p><pre class="literal-block">
003B5DA0
003B5DA4
</pre><p><strong>&#x7ED3;&#x8BBA;&#x5C31;&#x662F;&#xFF0C;Right *robj = bobj;&#x65F6;&#xFF0C;&#x7F16;&#x8BD1;&#x5668;&#x8FD4;&#x56DE;&#x4E86;bobj&#x7684;&#x4E00;&#x4E2A;&#x504F;&#x79FB;&#x5730;&#x5740;&#x3002;</strong> &#x4ECE;&#x8BED;&#x8A00;&#x89D2;&#x5EA6;&#x770B;&#xFF0C;&#x5C31;&#x662F;&#x8FD9;&#x4E2A;&#x8F6C;&#x6362;&#xFF0C;&#x8FD4;&#x56DE;&#x4E86;bobj&#x4E2D;Right*&#x7684;&#x90A3;&#x4E00;&#x90E8;&#x5206;&#x7684;&#x8D77;&#x59CB;&#x5730;&#x5740;&#x3002;&#x4F46;&#x7F16;&#x8BD1;&#x5668;&#x5E76;&#x4E0D;&#x603B;&#x662F;&#x5728;bobj&#x4E0A;&#x52A0;&#x4E00;&#x4E2A;&#x504F;&#x79FB;&#xFF0C;&#x4F8B;&#x5982;:</p><pre class="literal-block">
bobj = NULL;
Right *robj = bobj;
</pre><p>&#x7F16;&#x8BD1;&#x5668;&#x4E0D;&#x4F1A;&#x50BB;&#x5230;&#x7ED9;&#x4F60;&#x4E00;&#x4E2A;0x00000004&#x7684;&#x5730;&#x5740;&#xFF0C;&#x8FD9;&#x7B80;&#x76F4;&#x6BD4;NULL&#x66F4;&#x65E0;&#x7406;&#x3002;</p><p><strong>void*&#x8F6C;&#x6362;</strong></p><p>&#x7F16;&#x8BD1;&#x5668;&#x5F53;&#x7136;&#x6709;&#x7406;&#x7531;&#x505A;&#x4E0A;&#x9762;&#x7684;&#x504F;&#x79FB;&#x8F6C;&#x6362;&#x3002;&#x90A3;&#x662F;&#x56E0;&#x4E3A;&#x5728;&#x7F16;&#x8BD1;&#x9636;&#x6BB5;&#xFF0C;&#x7F16;&#x8BD1;&#x5668;&#x5C31;&#x77E5;&#x9053;bobj&#x548C;Right&#x4E4B;&#x95F4;&#x7684;&#x5173;&#x7CFB;&#x3002;&#x8FD9;&#x4E2A;&#x504F;&#x79FB;&#x91CF;&#x751A;&#x81F3;&#x4E0D;&#x9700;&#x8981;&#x5728;&#x8FD0;&#x884C;&#x671F;&#x95F4;&#x52A8;&#x6001;&#x8BA1;&#x7B97;&#xFF0C;&#x6216;&#x662F;&#x4ECE;&#x67D0;&#x4E2A;&#x5730;&#x65B9;&#x53D6;&#x3002;&#x5982;&#x679C;&#x4F60;&#x770B;&#x8FC7;&#x4E0A;&#x9762;&#x4EE3;&#x7801;&#x5BF9;&#x5E94;&#x7684;&#x6C47;&#x7F16;&#x6307;&#x4EE4;&#xFF0C;&#x76F4;&#x63A5;&#x5C31;&#x662F;:</p><pre class="literal-block">
add eax, 4 ; &#x76F4;&#x63A5;&#x52A0; sizeof(Left)&#xFF0C;&#x8BB0;&#x4F4F;&#xFF0C;Right&#x5728;Left&#x4E4B;&#x540E;
</pre><p>void*&#x5C31;&#x6CA1;&#x90A3;&#x4E48;&#x5E78;&#x8FD0;&#x4E86;&#x3002;void*&#x548C;Bottom&#x6CA1;&#x6709;&#x4EFB;&#x4F55;&#x5173;&#x7CFB;&#xFF0C;&#x6240;&#x4EE5;:</p><pre class="literal-block">
void *vobj = bobj; // vobj&#x7684;&#x5730;&#x5740;&#x548C;bobj&#x5B8C;&#x5168;&#x76F8;&#x540C;
</pre><p>&#x7136;&#x540E;&#x5F53;&#x4F60;&#x5C06;vobj&#x8F6C;&#x6362;&#x5230;&#x4E00;&#x4E2A;Right*&#x4F7F;&#x7528;&#x65F6;:</p><pre class="literal-block">
robj = (Right*) vobj;  // &#x6CA1;&#x6709;&#x504F;&#x79FB;&#x8F6C;&#x6362;&#xFF0C;robj == vobj == bobj
robj-&gt;rdisplay();
</pre><p>robj&#x6307;&#x5411;&#x7684;&#x662F;Bottom&#x7684;&#x8D77;&#x59CB;&#x5730;&#x5740;&#xFF0C;&#x5929;&#x554A;&#xFF0C;&#x5728;&#x6211;&#x4EEC;&#x5B66;&#x4E60;C++&#x65F6;&#xFF0C;&#x6211;&#x4EEC;&#x53EF;&#x4EE5;&#x8BF4;Bottom&#x5C31;&#x662F;&#x4E00;&#x4E2A;Left&#xFF0C;&#x4E5F;&#x662F;&#x4E00;&#x4E2A;Right&#xFF0C;&#x6240;&#x8C13;&#x7684;is kind of&#x3002;&#x4F46;&#x8FD9;&#x91CC;&#x7684;&#x60B2;&#x5267;&#x5728;&#x4E8E;&#xFF0C;&#x6309;&#x7167;&#x4E0A;&#x9762;&#x7684;&#x903B;&#x8F91;&#xFF0C;&#x6211;&#x4EEC;&#x5728;&#x4F7F;&#x7528;Right&#x65F6;&#xFF0C;&#x5176;&#x5B9E;&#x5E94;&#x8BE5;&#x4F7F;&#x7528;Bottom&#x91CC;Right&#x90A3;&#x4E00;&#x90E8;&#x5206;&#x3002; <strong>&#x4F46;&#x73B0;&#x5728;&#x8FD9;&#x4E2A;&#x8F6C;&#x6362;&#xFF0C;&#x5374;&#x8BA9;robj&#x6307;&#x5411;&#x4E86;Bottom&#x91CC;Left&#x90A3;&#x4E00;&#x90E8;&#x5206;&#x3002;</strong></p><p>&#x5F53;&#x8C03;&#x7528; <tt class="docutils literal"><span class="pre">robj-&gt;rdisplay</span></tt> &#x65F6;&#xFF0C;&#x7F16;&#x8BD1;&#x5668;&#x5F53;&#x7136;&#x6309;&#x7167;Right&#x7684;&#x5185;&#x5B58;&#x5E03;&#x5C40;&#xFF0C;&#x751F;&#x6210;&#x4E00;&#x4E2A;&#x865A;&#x51FD;&#x6570;&#x7684;&#x8C03;&#x7528;&#x6307;&#x4EE4;&#xFF0C;&#x5927;&#x6982;&#x5C31;&#x662F;:</p><pre class="literal-block">
mov vptr, robj-&gt;[0] ;; vptr&#x5728;robj&#x8D77;&#x59CB;&#x5730;&#x5740;&#x5904;
mov eax, vptr[0] ;; rdisplay&#x5728;vtable&#x4E2D;&#x4F4D;&#x4E8E;&#x7B2C;&#x4E00;&#x4E2A;
mov ecx, robj
call eax
</pre><p>&#x603B;&#x800C;&#x8A00;&#x4E4B;&#xFF0C; <tt class="docutils literal"><span class="pre">robj-&gt;rdisplay</span></tt> &#x5C31;&#x662F;&#x4F7F;&#x7528;&#x504F;&#x79FB;0&#x5904;&#x7684;&#x503C;&#x4F5C;&#x4E3A;vptr&#xFF0C;&#x7136;&#x540E;&#x4F7F;&#x7528;vptr&#x6307;&#x5411;&#x7684;vtable&#x4E2D;&#x7B2C;&#x4E00;&#x4E2A;&#x51FD;&#x6570;&#x4F5C;&#x4E3A;&#x8C03;&#x7528;&#x3002;</p><p>&#x4F46;&#xFF0C;robj&#x6B63;&#x6307;&#x5411;bobj&#x7684;&#x8D77;&#x59CB;&#x5730;&#x5740;&#xFF0C;&#x8FD9;&#x4E2A;&#x5730;&#x5740;&#x662F;&#x653E;&#x7F6E;Left_vptr&#x7684;&#x5730;&#x65B9;&#x3002;&#x8FD9;&#x4E2A;&#x8FC7;&#x7A0B;&#xFF0C;&#x4F7F;&#x7528;&#x4E86;Left_ptr&#xFF0C;&#x800C;Left_ptr&#x6307;&#x5411;&#x7684;vtable&#x4E2D;&#xFF0C;&#x7B2C;&#x4E00;&#x4E2A;&#x51FD;&#x6570;&#x662F;&#x4EC0;&#x4E48;&#x5462;&#xFF1F;:</p><pre class="literal-block">
Left_vptr ---&gt;  +---------------------+
                | 0: Bottom::ldisplay |
                +---------------------+
</pre><p>&#x6B63;&#x662F;Bottom::ldisplay&#xFF01;&#x5230;&#x8FD9;&#x91CC;&#xFF0C;&#x6574;&#x4E2A;&#x95EE;&#x9898;&#x7684;&#x539F;&#x56E0;&#x5C31;&#x88AB;&#x68B3;&#x7406;&#x51FA;&#x6765;&#x4E86;&#x3002;</p><p>;;END;;</p></div></div><img src ="http://www.cppblog.com/kevinlynx/aggbug/145409.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/kevinlynx/" target="_blank">Kevin Lynx</a> 2011-04-30 20:14 <a href="http://www.cppblog.com/kevinlynx/archive/2011/04/30/145409.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>逆向思路：破解飞秋群聊协议</title><link>http://www.cppblog.com/kevinlynx/archive/2011/01/23/139187.html</link><dc:creator>Kevin Lynx</dc:creator><author>Kevin Lynx</author><pubDate>Sun, 23 Jan 2011 13:01:00 GMT</pubDate><guid>http://www.cppblog.com/kevinlynx/archive/2011/01/23/139187.html</guid><wfw:comment>http://www.cppblog.com/kevinlynx/comments/139187.html</wfw:comment><comments>http://www.cppblog.com/kevinlynx/archive/2011/01/23/139187.html#Feedback</comments><slash:comments>8</slash:comments><wfw:commentRss>http://www.cppblog.com/kevinlynx/comments/commentRss/139187.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/kevinlynx/services/trackbacks/139187.html</trackback:ping><description><![CDATA[<p><font size=2></font></p>
<p><strong>题外</strong></p>
<p>飞秋是一款局域网内的IM软件，界面类似QQ，实现上与飞鸽（<a href="http://ipmsg.org/index.html.en" target=_blank>IP message</a>）有点渊源，免费，不开源。</p>
<p>公司大概两年前开始使用这款软件作为员工之间办公吹牛的工具。最近游戏玩得少，就想彻底换到linux下，</p>
<p>组内也有其他两人是llinux-er，不过悲剧的是换到linux下就无法收取飞秋群里的聊天信息了，不免寂寞。</p>
<p>所以，就想写个协议兼容的程序（或者说改个东西）来收取群信息。</p>
<p>&nbsp;</p>
<p><strong>准备</strong></p>
<p>我本身并不擅长逆向工程，破解什么的纯碎业余。在GOOGLE/BAIDU之后发现并没有前人留下的成果。</p>
<p>使用抓包程序，以及综合网络上的信息，还是可以得出不少有用的信息：</p>
<p># 飞秋兼容了飞鸽的协议，其协议格式基本上基于文本形式，各个内容之间使用冒号作为分隔，例如：</p>
<blockquote>
<p>1:100:user:pcname:32:message_body</p>
</blockquote>
<p># 飞秋在私聊情况下的消息内容是没有加密的，但群聊信息加密了，解密这个内容也是我的目标所在</p>
<p># 在抓包软件下根据消息的目标IP地址可以推断飞秋发送群信息是基于UDP组播的，即就算你不是这个群</p>
<p>&nbsp;&nbsp; 的成员，也会收到群消息</p>
<p>有用的文章： <a href="http://www.javaeye.com/topic/810507" target=_blank>《局域网内实现飞鸽欺骗》</a>、<a href="http://www.freeeim.com/f/?695.html" target=_blank>《飞鸽传书数据加密分析》</a>(个人感觉没有任何实质内容，而</p>
<p>且飞鸽传书并不是飞秋，属于误导性文章，但是依然有用）。最重要的，稍微浏览IP message的代码，</p>
<p>以及linux下的iptux（另一个兼容飞鸽的局域网IM）代码，都是对接下来的破解有益的。</p>
<p>&nbsp;</p>
<p><strong>破解</strong></p>
<p>我希望我提供更多的，是一种crack的思路，虽然我不是一个cracker。破解和写程序不太一样，它需要</p>
<p>更多的耐心、运气、程序之外的思考。如前所做的准备，尤其重要。</p>
<p>工具及环境：飞秋2.4版本、OllyDbg，为了方便接收群信息，最好有两台电脑。</p>
<p>&nbsp;</p>
<p><strong>STEP 1 找入手点</strong></p>
<p>在开始面对一大推汇编代码时，我们需要一个最接近目标的点。获取这个点根据目标的不同而不同。例如，</p>
<p>这里主要是针对网络数据的解密。所以，最直接的就是去找这些网络数据。当然，根据具体程序的表现，也</p>
<p>可以从其他点入手，例如飞秋收到群消息后会在任务栏闪烁图标，也可以从这个功能逆向过去。</p>
<p>&nbsp;</p>
<p>因为飞秋使用UDP协议，所以我们可以在recvfrom函数下断点（bp recvfrom）。因为接收UDP包的接口</p>
<p>可能还有WSARecvFrom，甚至winsock1.0中的recvfrom，所以最好都下断点。另一台机器发送群消息，</p>
<p>程序在winsock1.0里的recvfrom断下来：</p>
<p>71A43001 &gt;&nbsp; 8BFF&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mov&nbsp;&nbsp;&nbsp;&nbsp; edi, edi<br>71A43003&nbsp;&nbsp;&nbsp; 55&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; push&nbsp;&nbsp;&nbsp; ebp<br>71A43004&nbsp;&nbsp;&nbsp; 8BEC&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mov&nbsp;&nbsp;&nbsp;&nbsp; ebp, esp<br>71A43006&nbsp;&nbsp;&nbsp; 51&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; push&nbsp;&nbsp;&nbsp; ecx
<p>这个不是我们需要的，我们需要根据这个点获得用户层代码，这将是整个破解过程的起点。所以，OD中</p>
<p>ALT+K查看调用堆栈，然后跳到调用recvfrom的函数处：</p>
<p>00490890&nbsp; /$&nbsp; 56&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; push&nbsp;&nbsp;&nbsp; esi&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ;&nbsp; 接收数据的函数入口<br>00490891&nbsp; |.&nbsp; 8B7424 08&nbsp;&nbsp;&nbsp;&nbsp; mov&nbsp;&nbsp;&nbsp;&nbsp; esi, dword ptr [esp+8]<br>00490895&nbsp; |.&nbsp; 8D46 10&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; lea&nbsp;&nbsp;&nbsp;&nbsp; eax, dword ptr [esi+10]<br>00490898&nbsp; |.&nbsp; 50&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; push&nbsp;&nbsp;&nbsp; eax&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ; /pFromLen<br>00490899&nbsp; |.&nbsp; 56&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; push&nbsp;&nbsp;&nbsp; esi&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ; |pFrom<br>0049089A&nbsp; |.&nbsp; C700 10000000 mov&nbsp;&nbsp;&nbsp;&nbsp; dword ptr [eax], 10&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ; |<br>004908A0&nbsp; |.&nbsp; 8B09&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mov&nbsp;&nbsp;&nbsp;&nbsp; ecx, dword ptr [ecx]&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ; |<br>004908A2&nbsp; |.&nbsp; 6A 00&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; push&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ; |Flags = 0<br>004908A4&nbsp; |.&nbsp; 8D46 18&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; lea&nbsp;&nbsp;&nbsp;&nbsp; eax, dword ptr [esi+18]&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ; |<br>004908A7&nbsp; |.&nbsp; 68 FF3F0000&nbsp;&nbsp; push&nbsp;&nbsp;&nbsp; 3FFF&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ; |BufSize = 3FFF (16383.)<br>004908AC&nbsp; |.&nbsp; 50&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; push&nbsp;&nbsp;&nbsp; eax&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ; |Buffer<br>004908AD&nbsp; |.&nbsp; 51&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; push&nbsp;&nbsp;&nbsp; ecx&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ; |Socket<br>004908AE&nbsp; |.&nbsp; E8 C7F30C00&nbsp;&nbsp; call&nbsp;&nbsp;&nbsp; &lt;jmp.&amp;WSOCK32.#17&gt;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ; \recvfrom
<p>邪恶的OD已经将调用recvfrom时传入参数的指令标记出来了。中文注释是我分析时加的。recvfrom里传入</p>
<p>的接收缓存，是我们应该关注的。如果能跟进这个缓存，假设程序的流程比较简单：接收了数据，然后在某个</p>
<p>地方直接解密，不管它的加密方式是什么，只要能找到这个缓存数据从加密变为解密的地方，对于整个破解而言，</p>
<p>都算是迈进了一大步。</p>
<p>于是，在00490890（上面找到的函数入口）下断点，准备跟进接收缓存。注意：在OD里调试跟在vc里不一样，</p>
<p>跳到调用堆栈里的某个函数，寄存器依然是当前的值。所以需要重新跟。</p>
<p>&nbsp;</p>
<p><strong>STEP 2 内存断点</strong></p>
<p>F9让程序继续运行，再次在另一台机器上发送群消息。这回程序在00490890处断下，然后跟接收缓存：</p>
<p>004908AC&nbsp; |.&nbsp; 50&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; push&nbsp;&nbsp;&nbsp; eax&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ; |Buffer = 0011F4CC<br>004908AD&nbsp; |.&nbsp; 51&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; push&nbsp;&nbsp;&nbsp; ecx&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ; |Socket<br>004908AE&nbsp; |.&nbsp; E8 C7F30C00&nbsp;&nbsp; call&nbsp;&nbsp;&nbsp; &lt;jmp.&amp;WSOCK32.#17&gt;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ; \recvfrom
<p>接收缓存Buffer的值为0011F4CC，如前所述，我们要跟进这个地址指向的内存的变化情况。F8单步运行到</p>
<p>recvfrom后，也就是接收了网络数据后，查看内存内容</p>
<p>(d 0011F4CC)：</p>
<p>0011F4CC&nbsp; 31 5F 6C 62 74 34 5F 30 23 31 32 38 23 38 38 41&nbsp; 1_lbt4_0#128#88A<br>0011F4DC&nbsp; 45 31 44 44 34 36 36 46 44 23 30 23 30 23 37 32&nbsp; E1DD466FD#0#0#72<br>0011F4EC&nbsp; 3A 31 32 39 35 37 32 31 32 31 33 3A 41 64 6D 69&nbsp; :1295721213:Admi<br>0011F4FC&nbsp; 6E 69 73 74 72 61 74 6F 72 3A 50 43 2D 32 30 31&nbsp; nistrator:PC-201<br>0011F50C&nbsp; 30 31 31 30 34 32 30 30 35 3A 34 31 39 34 33 33&nbsp; 011042005:419433<br>0011F51C&nbsp; 39 3A 5E 3B 83 A1 14 6D A4 D2 E3 D8 E8 AB B1 3A&nbsp; 9:^;儭mひ阖璜?<br>0011F52C&nbsp; 5B BC C2 FE E9 DA CB DD 00 BC 59 FC 9D A7 D7 91&nbsp; [悸谒?糦鼭ё
<p>内容开头正是飞秋的协议头，未加密，不过没多大用。根据之前获取的飞秋协议，可知，在0011F51E</p>
<p>处就是聊天内容的密文。</p>
<p>很自然地，为了监视这段内存的变化情况，在该位置下内存访问断点（右击数据区即可看到下断点的选项）。</p>
<p>F9继续走，然后马上断下来：</p>
<p>0049010F&nbsp; |.&nbsp; F3:A5&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; rep&nbsp;&nbsp;&nbsp;&nbsp; movs dword ptr es:[edi], dword ptr [&gt;<br>00490111&nbsp; |.&nbsp; 8B4A 04&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mov&nbsp;&nbsp;&nbsp;&nbsp; ecx, dword ptr [edx+4]<br>00490114&nbsp; |.&nbsp; C74424 24 000&gt;mov&nbsp;&nbsp;&nbsp;&nbsp; dword ptr [esp+24], 0<br>0049011C&nbsp; |.&nbsp; 894D 64&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mov&nbsp;&nbsp;&nbsp;&nbsp; dword ptr [ebp+64], ecx<br>0049011F&nbsp; |.&nbsp; 33C9&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; xor&nbsp;&nbsp;&nbsp;&nbsp; ecx, ecx
<p>程序到了一个陌生的环境（在这种满世界都是汇编代码的情况下，几乎一不小心就会迷失其中），看了下</p>
<p>附近的代码，没什么可疑。通过下内存访问断点的思路，似乎显得荆棘丛生。</p>
<p>&nbsp;</p>
<p><strong>STEP 3 靠近目标</strong></p>
<p>不妨冷静下来思考，如果没有直接的路，我们可能需要悲惨地大海捞针。在写一个网络程序时，网络底层</p>
<p>收到数据包，无非要么直接进行逻辑处理，要么缓存到一个逻辑处理队列里稍后处理。后者虽然对于程序员</p>
<p>而言是个好方法，但是因为跨了线程，又涉及到队列缓存，对于逆向而言，绝对是悲剧。</p>
<p>&nbsp;</p>
<p>但是如果使用了前者呢？对于一个网络客户端程序而言，也许直接进行逻辑处理才是最KISS的方法。（这种猜测</p>
<p>的破解方式，绝对需要运气。）所以，如果是直接进行处理，那么在接收到网络数据附近，必然就有解密函数。</p>
<p>所以，不妨顺着收包函数附近随意浏览一番。（但不要走进太深的call，不然又迷失了。）</p>
<p>0048FE10&nbsp; /$&nbsp; B8 18400000&nbsp;&nbsp; mov&nbsp;&nbsp;&nbsp;&nbsp; eax, 4018&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>0048FE15&nbsp; |.&nbsp; E8 560A0C00&nbsp;&nbsp; call&nbsp;&nbsp;&nbsp; 00550870<br>0048FE1A&nbsp; |.&nbsp; 8D4424 00&nbsp;&nbsp;&nbsp;&nbsp; lea&nbsp;&nbsp;&nbsp;&nbsp; eax, dword ptr [esp]<br>0048FE1E&nbsp; |.&nbsp; 56&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; push&nbsp;&nbsp;&nbsp; esi<br>0048FE1F&nbsp; |.&nbsp; 8BF1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mov&nbsp;&nbsp;&nbsp;&nbsp; esi, ecx<br>0048FE21&nbsp; |.&nbsp; 50&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; push&nbsp;&nbsp;&nbsp; eax<br>0048FE22&nbsp; |.&nbsp; E8 690A0000&nbsp;&nbsp; call&nbsp;&nbsp;&nbsp; 00490890&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ;&nbsp; 接收网络数据
<p>0048FE10函数调用了刚才发现的收包函数。这个函数在收完数据后，不久就调用了另一个函数：</p>
<p>0048FE3F&nbsp; |.&nbsp; 51&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; push&nbsp;&nbsp;&nbsp; ecx<br>0048FE40&nbsp; |.&nbsp; 52&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; push&nbsp;&nbsp;&nbsp; edx<br>0048FE41&nbsp; |.&nbsp; 8BCE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mov&nbsp;&nbsp;&nbsp;&nbsp; ecx, esi<br>0048FE43&nbsp; |.&nbsp; E8 88020000&nbsp;&nbsp; call&nbsp;&nbsp;&nbsp; 004900D0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ;&nbsp; 似乎很可疑？
<p>进到004900D0函数，发现这个函数真TMD巨大。随意浏览之，发现OD有这种提示：</p>
<p>00490178&nbsp; |.&nbsp; 68 34FD5E00&nbsp;&nbsp; push&nbsp;&nbsp;&nbsp; 005EFD34&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ;&nbsp; ASCII "_lbt"<br>0049017D&nbsp; |.&nbsp; 8D4C24 14&nbsp;&nbsp;&nbsp;&nbsp; lea&nbsp;&nbsp;&nbsp;&nbsp; ecx, dword ptr [esp+14]<br>00490181&nbsp; |.&nbsp; 89BC24 544000&gt;mov&nbsp;&nbsp;&nbsp;&nbsp; dword ptr [esp+4054], edi
<p>_lbt，恩，消息头里有这个字符串标识。估计是在做些消息头的逻辑操作。这个函数太长，里面还有若干call，
<p>可谓头大。所以说，代码写得丑，可读性差，对于防破解还是颇有益处的。跳回到0048FE43，发现当前
<p>函数基本完了。
<p>&nbsp;
<p>于是往上看，来到调用这个函数的地方：
<p>0050F647&nbsp; |.&nbsp; E8 C407F8FF&nbsp;&nbsp; call&nbsp;&nbsp;&nbsp; 0048FE10<br>0050F64C&nbsp; |.&nbsp; BF 01000000&nbsp;&nbsp; mov&nbsp;&nbsp;&nbsp;&nbsp; edi, 1
<p>回顾下，我们有函数A接收网络数据，有函数B调用A，现在回到了C，来到了调用B的地方0050F647。C函数
<p>也很巨大，直接往下浏览，会发现一些switch语句：
<p>0050F71A&nbsp; |.&nbsp; 81E6 FF000000 and&nbsp;&nbsp;&nbsp;&nbsp; esi, 0FF<br>0050F720&nbsp; |.&nbsp; 8D46 FF&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; lea&nbsp;&nbsp;&nbsp;&nbsp; eax, dword ptr [esi-1]&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ;&nbsp; Switch (cases 1..D3)<br>0050F723&nbsp; |.&nbsp; 3D D2000000&nbsp;&nbsp; cmp&nbsp;&nbsp;&nbsp;&nbsp; eax, 0D2<br>0050F728&nbsp; |.&nbsp; 0F87 8C000000 ja&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0050F7BA<br>0050F72E&nbsp; |.&nbsp; 33C9&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; xor&nbsp;&nbsp;&nbsp;&nbsp; ecx, ecx<br>0050F730&nbsp; |.&nbsp; 8A88 60315100 mov&nbsp;&nbsp;&nbsp;&nbsp; cl, byte ptr [eax+513160]<br>0050F736&nbsp; |.&nbsp; FF248D 403051&gt;jmp&nbsp;&nbsp;&nbsp;&nbsp; dword ptr [ecx*4+513040]<br>0050F73D&nbsp; |&gt;&nbsp; 8D9424 F40000&gt;lea&nbsp;&nbsp;&nbsp;&nbsp; edx, dword ptr [esp+F4]&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ;&nbsp; Case 1 of switch 0050F720
<p>往后浏览下这个switch&#8230;case，发现非常之大，这个函数也因此非常巨大。不妨猜测这个是根据不同消息做不同
<p>逻辑处理的地方。这个想法正是给予我们灵感的关键。
<p>&nbsp;
<p>群聊消息必然也有个类型，通过之前OD获取到的网络数据（或者截取网络封包所得），群聊消息的类型为：4194339
<p>（16进制400023H），去掉高位，也就是23H。仔细地对比每一个case，果然发现了一段处理代码：
<p>00512787&nbsp; |&gt; \39AC24 640100&gt;cmp&nbsp;&nbsp;&nbsp;&nbsp; dword ptr [esp+164], ebp&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ;&nbsp; Case 23 of switch 0050F720<br>0051278E&nbsp; |.&nbsp; 75 07&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; jnz&nbsp;&nbsp;&nbsp;&nbsp; short 00512797&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ;&nbsp; 群聊天处理<br>00512790&nbsp; |.&nbsp; 8BC7&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mov&nbsp;&nbsp;&nbsp;&nbsp; eax, edi<br>00512792&nbsp; |.&nbsp; E9 8C080000&nbsp;&nbsp; jmp&nbsp;&nbsp;&nbsp;&nbsp; 00513023
<p>这个代码之下的处理也有不少代码。在不涉及到更多细节之前，我们可以大胆地将注意力放在接下来的call上。从这个case</p>
<p>往下，第一个call为：</p>
<p>005127E6&nbsp; |.&nbsp; 50&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; push&nbsp;&nbsp;&nbsp; eax<br>005127E7&nbsp; |.&nbsp; E8 A4A20000&nbsp;&nbsp; call&nbsp;&nbsp;&nbsp; 0051CA90&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ;&nbsp; 非常可疑<br>005127EC&nbsp; |.&nbsp; B8 01000000&nbsp;&nbsp; mov&nbsp;&nbsp;&nbsp;&nbsp; eax, 1<br>005127F1&nbsp; |.&nbsp; E9 2D080000&nbsp;&nbsp; jmp&nbsp;&nbsp;&nbsp;&nbsp; 00513023
<p>&nbsp;</p>
<p><strong>STEP 4 多尝试</strong></p>
<p>有怀疑，就用事实来证明。果断地在005127E6处下断点。然后发群消息，程序断下来。因为这个函数压入了
<p>eax作为参数，且对ecx做了赋值：
<p>005127E4&nbsp; |.&nbsp; 8BCB&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mov&nbsp;&nbsp;&nbsp;&nbsp; ecx, ebx<br>005127E6&nbsp; |.&nbsp; 50&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; push&nbsp;&nbsp;&nbsp; eax<br>005127E7&nbsp; |.&nbsp; E8 A4A20000&nbsp;&nbsp; call&nbsp;&nbsp;&nbsp; 0051CA90&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ;&nbsp; 非常可疑
<p>在调用一个函数前对ecx做赋值，一般都是C++成员函数调用。eax作为一个参数，非常值得关注，果断查看eax
<p>指向的内存值：
<p>001235C8&nbsp; 41 64 6D 69 6E 69 73 74 72 61 74 6F 72 00 6D 00&nbsp; Administrator.m.<br>001235D8&nbsp; 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00&nbsp; ................<br>001235E8&nbsp; 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00&nbsp; ................<br>001235F8&nbsp; 00 00 50 43 2D 32 30 31 30 31 31 30 34 32 30 30&nbsp; ..PC-20101104200<br>00123608&nbsp; 35 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00&nbsp; 5...............<br>00123618&nbsp; 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00&nbsp; ................<br>00123628&nbsp; 8A 7B 00 00 C0 A8 00 03 09 79 00 00 01 00 00 00&nbsp; 妠..括..y.....<br>00123638&nbsp; 04 00 00 00 00 00 00 00 80 00 00 00 38 38 41 45&nbsp; .......€...88AE<br>00123648&nbsp; 31 44 44 34 36 36 46 44 00 00 00 00 00 00 00 00&nbsp; 1DD466FD........<br>00123658&nbsp; 00 00 00 00 F4 C7 23 00 FD 22 3B 4D 23 00 40 00&nbsp; ....羟#.?;M#.@.<br>00123668&nbsp; 49 00 00 00 36 00 00 00 5E 3B 83 A1 14 6D A4 D2&nbsp; I...6...^;儭mひ
<p>有用户名、机器名、发送者MAC地址，这么多可疑信息。完全可以猜测，eax传入的是一个结构体地址，</p>
<p>当然对象地址也可以，反正是个复杂数据结构。更重要的，在这块内存往下不远处，果断地发现了从</p>
<p>网络接收到的加密的聊天内容：</p>
<p>00123670&nbsp; 5E 3B 83 A1 14 6D A4 D2 E3 D8 E8 AB B1 3A 5B BC&nbsp; ^;儭mひ阖璜?[<br>00123680&nbsp; C2 FE E9 DA CB DD 00 BC 59 FC 9D A7 D7 91 CF 5A&nbsp; 漫橼溯.糦鼭ё懴Z
<p>F8直接步过0051CA90函数。任务栏开始出现闪烁的图标（虽然没有闪），上面的内存数据变了：
<p>00123670&nbsp; 74 65 73 74 7B 2F 66 6F 6E 74 3B 2D 31 34 20 30&nbsp; test{/font;-14 0<br>00123680&nbsp; 20 30 20 30 20 34 30 30 20 30 20 30 20 30 20 31&nbsp;&nbsp; 0 0 400 0 0 0 1<br>00123690&nbsp; 33 34 20 33 20 32 20 31 20 32 20 CB CE CC E5 20&nbsp; 34 3 2 1 2 宋体
<p>test正是我发的内容。</p>
<p>&nbsp;</p>
<p><strong>STEP 5 缩小范围</strong></p>
<p>实际上走到这里，凭借运气和程序编写的常识，已经找到关键点。不过看来0051CA90这个函数做的事情
<p>有点多，除了解密内容似乎还有UI上的一些处理（例如那个闪烁的任务栏图标）。所以，我们要做的是，进一步
<p>跟进，找到关键点。
<p>&nbsp;
<p>现在缩小范围要容易得多，因为我们得到了一个会变化的内存地址：00123670。只需要F8一步一步地
<p>运行代码，一旦发现内存内容改变，则可以进一步进如，从而找到关键call。具体过程我就不给了，大概为：
<p>0051CB02&nbsp; |.&nbsp; 52&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; push&nbsp;&nbsp;&nbsp; edx<br>0051CB03&nbsp; |.&nbsp; 68 00400000&nbsp;&nbsp; push&nbsp;&nbsp;&nbsp; 4000<br>0051CB08&nbsp; |.&nbsp; 56&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; push&nbsp;&nbsp;&nbsp; esi<br>0051CB09&nbsp; |.&nbsp; 50&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; push&nbsp;&nbsp;&nbsp; eax<br>0051CB0A&nbsp; |.&nbsp; 56&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; push&nbsp;&nbsp;&nbsp; esi<br>0051CB0B&nbsp; |.&nbsp; E8 F041F7FF&nbsp;&nbsp; call&nbsp;&nbsp;&nbsp; 00490D00&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ; 跟进</p>
<p>&nbsp;</p>
<p>00490DB0&nbsp; |.&nbsp; 6A 00&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; push&nbsp;&nbsp;&nbsp; 0<br>00490DB2&nbsp; |.&nbsp; 83E1 03&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; and&nbsp;&nbsp;&nbsp;&nbsp; ecx, 3<br>00490DB5&nbsp; |.&nbsp; 6A 22&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; push&nbsp;&nbsp;&nbsp; 22<br>00490DB7&nbsp; |.&nbsp; F3:A4&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; rep&nbsp;&nbsp;&nbsp;&nbsp; movs byte ptr es:[edi], byte ptr [es&gt;<br>00490DB9&nbsp; |.&nbsp; 8BBC24 344000&gt;mov&nbsp;&nbsp;&nbsp;&nbsp; edi, dword ptr [esp+4034]<br>00490DC0&nbsp; |.&nbsp; 50&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; push&nbsp;&nbsp;&nbsp; eax&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ;&nbsp; 数据长度<br>00490DC1&nbsp; |.&nbsp; 8D4424 20&nbsp;&nbsp;&nbsp;&nbsp; lea&nbsp;&nbsp;&nbsp;&nbsp; eax, dword ptr [esp+20]<br>00490DC5&nbsp; |.&nbsp; 57&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; push&nbsp;&nbsp;&nbsp; edi&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ;&nbsp; 输出缓存<br>00490DC6&nbsp; |.&nbsp; 50&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; push&nbsp;&nbsp;&nbsp; eax&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ;&nbsp; 输入缓存（加密内容）<br>00490DC7&nbsp; |.&nbsp; 8D4C24 20&nbsp;&nbsp;&nbsp;&nbsp; lea&nbsp;&nbsp;&nbsp;&nbsp; ecx, dword ptr [esp+20]<br>00490DCB&nbsp; |.&nbsp; E8 5049F7FF&nbsp;&nbsp; call&nbsp;&nbsp;&nbsp; 00405720&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ;&nbsp; 最终解密函数
<p>&nbsp;</p>
<p>00405720函数内的实现基本上全是数据操作指令。加解密算法无非就是捣鼓这些数据，所以当我进到</p>
<p>00405720函数时，基本上可以断定它就是最终的解密函数。</p>
<p>&nbsp;</p>
<p><strong>STEP 6 解密</strong></p>
<p>事实上我们并不需要去弄懂它的具体解密算法，就算是直接的C++代码，没有算法论文的话也很难看懂，更何况</p>
<p>是这里的汇编。最直接的方式，就是查看这个解密函数对外界的依赖情况，例如需要的参数，this里是否有依赖</p>
<p>的数据。在了解了这些情况后，大可以将这段汇编复制出来直接作为C++内嵌汇编代码使用。</p>
<p>&nbsp;</p>
<p>不过，这里我想到了更简单的方式。因为我注意到飞秋和飞鸽在实现上有着不解之缘，而且我琢磨着作者也不会</p>
<p>为了一个加解密不太重要的应用场合而去整些高深的算法。我想到，飞秋也许会直接使用飞鸽里的加解密代码！</p>
<p>&nbsp;</p>
<p>在IP message的源码里，有blowfish加密算法的实现，我们来看接口：</p>
<p>class CBlowFish<br>{<br>private:<br>&nbsp;&nbsp;&nbsp; DWORD&nbsp;&nbsp;&nbsp; *PArray;<br>&nbsp;&nbsp;&nbsp; DWORD&nbsp;&nbsp;&nbsp; (*SBoxes)[256];<br>&nbsp;&nbsp;&nbsp; void&nbsp;&nbsp;&nbsp; Blowfish_encipher(DWORD *xl, DWORD *xr);<br>&nbsp;&nbsp;&nbsp; void&nbsp;&nbsp;&nbsp; Blowfish_decipher(DWORD *xl, DWORD *xr);
<p>public:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CBlowFish(const BYTE *key=NULL, int keybytes=0);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ~CBlowFish();<br>&nbsp;&nbsp;&nbsp; void&nbsp;&nbsp;&nbsp; Initialize(const BYTE *key, int keybytes);<br>&nbsp;&nbsp;&nbsp; DWORD&nbsp;&nbsp;&nbsp; GetOutputLength(DWORD lInputLong, int mode);<br>&nbsp;&nbsp;&nbsp; DWORD&nbsp;&nbsp;&nbsp; Encrypt(const BYTE *pInput, BYTE *pOutput, DWORD lSize, int mode=BF_CBC|BF_PKCS5, _int64 IV=0);<br>&nbsp;&nbsp;&nbsp; DWORD&nbsp;&nbsp;&nbsp; Decrypt(const BYTE *pInput, BYTE *pOutput, DWORD lSize, int mode=BF_CBC|BF_PKCS5, _int64 IV=0);<br>};
<p>从接口实现来说算是简洁漂亮友好和谐。我也用Decrypt这个函数的参数比对了上面没找到的那个call（00405720），</p>
<p>因为这里只是怀疑这个call就是这里的Decrypt，但并没有确切的证据。不过，对比下他们的参数就可以非常肯定了：</p>
00490DB0&nbsp; |.&nbsp; 6A 00&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; push&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ;参数IV<br>00490DB2&nbsp; |.&nbsp; 83E1 03&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; and&nbsp;&nbsp;&nbsp;&nbsp; ecx, 3<br>00490DB5&nbsp; |.&nbsp; 6A 22&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; push&nbsp;&nbsp;&nbsp; 22&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ;参数mode<br>00490DB7&nbsp; |.&nbsp; F3:A4&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; rep&nbsp;&nbsp;&nbsp;&nbsp; movs byte ptr es:[edi], byte ptr [es&gt;<br>00490DB9&nbsp; |.&nbsp; 8BBC24 344000&gt;mov&nbsp;&nbsp;&nbsp;&nbsp; edi, dword ptr [esp+4034]<br>00490DC0&nbsp; |.&nbsp; 50&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; push&nbsp;&nbsp;&nbsp; eax&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ; 参数 数据长度<br>00490DC1&nbsp; |.&nbsp; 8D4424 20&nbsp;&nbsp;&nbsp;&nbsp; lea&nbsp;&nbsp;&nbsp;&nbsp; eax, dword ptr [esp+20]<br>00490DC5&nbsp; |.&nbsp; 57&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; push&nbsp;&nbsp;&nbsp; edi&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ;&nbsp; 参数输出缓存<br>00490DC6&nbsp; |.&nbsp; 50&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; push&nbsp;&nbsp;&nbsp; eax&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ; 参数输入缓存（加密内容）<br>00490DC7&nbsp; |.&nbsp; 8D4C24 20&nbsp;&nbsp;&nbsp;&nbsp; lea&nbsp;&nbsp;&nbsp;&nbsp; ecx, dword ptr [esp+20]<br>00490DCB&nbsp; |.&nbsp; E8 5049F7FF&nbsp;&nbsp; call&nbsp;&nbsp;&nbsp; 00405720&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ;&nbsp; 最终解密函数
<p>最重要的，是参数mode。Decrypt默认参数mode是BF_CBC|BF_PKCS5的位组合，结果，恰好为22！</p>
<p>所以，基本上可以断定：飞秋的加解密实现，就是使用了IP message的blowfish算法：blowfish.cpp/h/h2。</p>
<p>&nbsp;</p>
<p><strong>STEP 7 密钥</strong></p>
<p>查看CBlowFish的使用，在解密前需要初始化，大概就是传入密钥之类。如果我们上面的猜测没有错，那么我们</p>
<p>从网络上取得的数据，然后取得密钥，直接使用blowfish的源码，就可以解密出消息内容。</p>
<p>&nbsp;</p>
<p>接下来的关键就是，找到这个密钥。关于这个密钥，之前在飞秋的配置文件FeiqCfg.xml里绕了很久的圈子，因为</p>
<p>发现加入一个群的时候，这个文件里就会多出一项很长的16进制字符串。也一度猜测密钥就是保存在这个字符串的</p>
<p>某个偏移里。接下来会让人大跌眼镜。</p>
<p>&nbsp;</p>
<p>因为CBlowFish这个类确实简单，使用它的最简单方式就是直接创建局部对象，然后传入key和keysize，即可完成</p>
<p>初始化。在之前展示的思路里，我也一度先去尝试最简单的方法。对于C++局部对象的创建，有个显著特征就是</p>
<p>mov ecx, dword ptr [esp+xxx]，也就是往ecx里写入一个栈地址。而且可以肯定的是，这个构造代码，必然发生</p>
<p>于call 00405720前面不远处，往上跟进：</p>
<p>00490D3F&nbsp; |&gt; \8B8C24 304000&gt;mov&nbsp;&nbsp;&nbsp;&nbsp; ecx, dword ptr [esp+4030]<br>00490D46&nbsp; |&gt;&nbsp; 51&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; push&nbsp;&nbsp;&nbsp; ecx&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>00490D47&nbsp; |.&nbsp; 52&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; push&nbsp;&nbsp;&nbsp; edx&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>00490D48&nbsp; |.&nbsp; 8D4C24 0C&nbsp;&nbsp;&nbsp;&nbsp; lea&nbsp;&nbsp;&nbsp;&nbsp; ecx, dword ptr [esp+C]<br>00490D4C&nbsp; |.&nbsp; E8 3F3DF7FF&nbsp;&nbsp; call&nbsp;&nbsp;&nbsp; 00404A90&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<p>一个压入两个参数的函数调用，对比CBlowFish的构造函数，刚好是2个参数。跟进00404A90：
<p>&nbsp;
<p>00404A90&nbsp; /$&nbsp; 56&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; push&nbsp;&nbsp;&nbsp; esi<br>00404A91&nbsp; |.&nbsp; 8BF1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mov&nbsp;&nbsp;&nbsp;&nbsp; esi, ecx<br>00404A93&nbsp; |.&nbsp; 6A 48&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; push&nbsp;&nbsp;&nbsp; 48<br>00404A95&nbsp; |.&nbsp; E8 69301600&nbsp;&nbsp; call&nbsp;&nbsp;&nbsp; 00567B03<br>00404A9A&nbsp; |.&nbsp; 68 00100000&nbsp;&nbsp; push&nbsp;&nbsp;&nbsp; 1000<br>00404A9F&nbsp; |.&nbsp; 8906&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mov&nbsp;&nbsp;&nbsp;&nbsp; dword ptr [esi], eax<br>00404AA1&nbsp; |.&nbsp; E8 5D301600&nbsp;&nbsp; call&nbsp;&nbsp;&nbsp; 00567B03
<p>&nbsp;</p>
<p>又是可爱的立即数！48H、1000H，这种特别的立即数总能让人安心，对比CBlowFish构造函数实现：</p>
<p>CBlowFish::CBlowFish (const BYTE *key, int keybytes)<br>{<br>&nbsp;&nbsp;&nbsp; PArray = new DWORD [NPASS + 2];//NPASS=16<br>&nbsp;&nbsp;&nbsp; SBoxes = new DWORD [4][256];
<p>&nbsp;&nbsp;&nbsp; if (key)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Initialize(key, keybytes);<br>}
<p>sizeof(DWORD)*18=48H，sizeof(DWORD)*4*256=1000H！这么极具喜剧意义的汇编C++代码映射，</p>
<p>基本可以肯定，00404AA1处，正是构造CBlowFish对象的地方，而构造的参数，正是我们魂牵梦萦的解密密钥：</p>
<p>&nbsp;</p>
<p>00490D46&nbsp; |&gt; \51&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; push&nbsp;&nbsp;&nbsp; ecx&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ;&nbsp; key长度<br>00490D47&nbsp; |.&nbsp; 52&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; push&nbsp;&nbsp;&nbsp; edx&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ;&nbsp; 密钥key<br>00490D48&nbsp; |.&nbsp; 8D4C24 0C&nbsp;&nbsp;&nbsp;&nbsp; lea&nbsp;&nbsp;&nbsp;&nbsp; ecx, dword ptr [esp+C]<br>00490D4C&nbsp; |.&nbsp; E8 3F3DF7FF&nbsp;&nbsp; call&nbsp;&nbsp;&nbsp; 00404A90&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ;&nbsp; 构造blowfish对象
<p>&nbsp;</p>
<p>在此处下断点，发送群消息，程序断下来，看看密钥究竟是什么。如果它确实是FeiqCfg.xml里的某个值，</p>
<p>那么我们还要进一步跟这个值具体在哪个配置项里。看看吧，密钥edx：</p>
<p>&nbsp;</p>
<blockquote>
<p>edx=00123644, (ASCII "88AE1DD466FD")</p>
</blockquote>
<p>&#160;</p>
<p>&#160;</p>
<p>TM的密钥居然是发送者的MAC地址！当我看到这个的时候我几乎快摔倒地上。如果飞秋使用一个MAC地址</p>
<p>作为密钥，那么这意味着：通过自己写的程序，可以取得局域网内其他群里的聊天内容！这个实在太邪恶了。</p>
<p>上回抓包的时候，虽然看不到内容，但可以看到美术、策划在群里聊的无比欢乐。这回有喜感了。</p>
<p>&nbsp;</p>
<p><strong>STEP END 可略</strong></p>
<p>看看时间，悲剧地发现整篇文章花了接近3个小时才写完。此刻我正踌躇要不要把代码发上来，但转念一想</p>
<p>最后那个STEP的发现实在让人蛋疼，所以还是算了。打算稍微封装下，然后使用这份代码在iptux 上改改包装</p>
<p>个界面，目的就算达成了。相信浏览完整篇文章，写出自己的代码不是什么大问题。</p>
<p>&nbsp;</p>
<p>其实我大可以直接给结论，但是我依然乐于分享过程和思路。一来算是自我总结记录（每次拿起OD，总是快捷</p>
<p>键一路忘）；二来也给有心人一个指引。</p>
<p>&nbsp;</p>
<p>最后，对这种东西还是有必要出个免责声明：根据本文章所造成的一切后果与文章作者无关。为了不糟蹋我这3个</p>
<p>小时的时间，转载麻烦注明出处。</p>
<p>PS，最后回顾下结论，其实发现这个解密非常非常简单。你说如果直接给卢本陶（飞秋作者）发封邮件，他会不</p>
<p>会直接告诉我？</p>
<img src ="http://www.cppblog.com/kevinlynx/aggbug/139187.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/kevinlynx/" target="_blank">Kevin Lynx</a> 2011-01-23 21:01 <a href="http://www.cppblog.com/kevinlynx/archive/2011/01/23/139187.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>一段tricky codes：函数调用的那些底层细节</title><link>http://www.cppblog.com/kevinlynx/archive/2011/01/02/137886.html</link><dc:creator>Kevin Lynx</dc:creator><author>Kevin Lynx</author><pubDate>Sun, 02 Jan 2011 08:34:00 GMT</pubDate><guid>http://www.cppblog.com/kevinlynx/archive/2011/01/02/137886.html</guid><wfw:comment>http://www.cppblog.com/kevinlynx/comments/137886.html</wfw:comment><comments>http://www.cppblog.com/kevinlynx/archive/2011/01/02/137886.html#Feedback</comments><slash:comments>4</slash:comments><wfw:commentRss>http://www.cppblog.com/kevinlynx/comments/commentRss/137886.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/kevinlynx/services/trackbacks/137886.html</trackback:ping><description><![CDATA[<p style="FONT-SIZE: 10pt"><br>有一天，被同事问到了下面这段代码，就简单分析了一下，发觉还有点意思：<br><br></p>
<div style="BORDER-BOTTOM: #cccccc 1px solid; BORDER-LEFT: #cccccc 1px solid; PADDING-BOTTOM: 4px; BACKGROUND-COLOR: #eeeeee; PADDING-LEFT: 4px; WIDTH: 98%; PADDING-RIGHT: 5px; FONT-SIZE: 13px; WORD-BREAK: break-all; BORDER-TOP: #cccccc 1px solid; BORDER-RIGHT: #cccccc 1px solid; PADDING-TOP: 4px"><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/None.gif"><span style="COLOR: #000000">__declspec(naked)<br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/None.gif"></span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000">&nbsp;call(</span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">&nbsp;pfn,&nbsp;<img src="http://www.cppblog.com/Images/dot.gif">)&nbsp;<br><img id=Codehighlighter1_45_172_Open_Image onclick="this.style.display='none'; Codehighlighter1_45_172_Open_Text.style.display='none'; Codehighlighter1_45_172_Closed_Image.style.display='inline'; Codehighlighter1_45_172_Closed_Text.style.display='inline';" align=top src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif"><img style="DISPLAY: none" id=Codehighlighter1_45_172_Closed_Image onclick="this.style.display='none'; Codehighlighter1_45_172_Closed_Text.style.display='none'; Codehighlighter1_45_172_Open_Image.style.display='inline'; Codehighlighter1_45_172_Open_Text.style.display='inline';" align=top src="http://www.cppblog.com/Images/OutliningIndicators/ContractedBlock.gif"></span><span style="BORDER-BOTTOM: #808080 1px solid; BORDER-LEFT: #808080 1px solid; BACKGROUND-COLOR: #ffffff; DISPLAY: none; BORDER-TOP: #808080 1px solid; BORDER-RIGHT: #808080 1px solid" id=Codehighlighter1_45_172_Closed_Text><img src="http://www.cppblog.com/Images/dot.gif"></span><span id=Codehighlighter1_45_172_Open_Text><span style="COLOR: #000000">{<br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif">&nbsp;&nbsp;&nbsp;&nbsp;__asm&nbsp;<br><img id=Codehighlighter1_62_170_Open_Image onclick="this.style.display='none'; Codehighlighter1_62_170_Open_Text.style.display='none'; Codehighlighter1_62_170_Closed_Image.style.display='inline'; Codehighlighter1_62_170_Closed_Text.style.display='inline';" align=top src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif"><img style="DISPLAY: none" id=Codehighlighter1_62_170_Closed_Image onclick="this.style.display='none'; Codehighlighter1_62_170_Closed_Text.style.display='none'; Codehighlighter1_62_170_Open_Image.style.display='inline'; Codehighlighter1_62_170_Open_Text.style.display='inline';" align=top src="http://www.cppblog.com/Images/OutliningIndicators/ContractedSubBlock.gif">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="BORDER-BOTTOM: #808080 1px solid; BORDER-LEFT: #808080 1px solid; BACKGROUND-COLOR: #ffffff; DISPLAY: none; BORDER-TOP: #808080 1px solid; BORDER-RIGHT: #808080 1px solid" id=Codehighlighter1_62_170_Closed_Text><img src="http://www.cppblog.com/Images/dot.gif"></span><span id=Codehighlighter1_62_170_Open_Text><span style="COLOR: #000000">{<br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pop&nbsp;eax;<br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;add&nbsp;eax,&nbsp;</span><span style="COLOR: #000000">3</span><span style="COLOR: #000000">;<br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;xchg&nbsp;dword&nbsp;ptr[esp],&nbsp;eax;<br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;push&nbsp;eax;<br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ret;<br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif">&nbsp;&nbsp;&nbsp;&nbsp;}</span></span><span style="COLOR: #000000"><br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockEnd.gif">}</span></span></div>
<p style="FONT-SIZE: 10pt">&nbsp;</p>
<p style="FONT-SIZE: 10pt">再看它的用法：</p>
<p style="FONT-SIZE: 10pt">&nbsp;</p>
<div style="BORDER-BOTTOM: #cccccc 1px solid; BORDER-LEFT: #cccccc 1px solid; PADDING-BOTTOM: 4px; BACKGROUND-COLOR: #eeeeee; PADDING-LEFT: 4px; WIDTH: 98%; PADDING-RIGHT: 5px; FONT-SIZE: 13px; WORD-BREAK: break-all; BORDER-TOP: #cccccc 1px solid; BORDER-RIGHT: #cccccc 1px solid; PADDING-TOP: 4px"><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/None.gif"><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000">&nbsp;print_str(&nbsp;</span><span style="COLOR: #0000ff">const</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #0000ff">char</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">s&nbsp;)<br><img id=Codehighlighter1_32_60_Open_Image onclick="this.style.display='none'; Codehighlighter1_32_60_Open_Text.style.display='none'; Codehighlighter1_32_60_Closed_Image.style.display='inline'; Codehighlighter1_32_60_Closed_Text.style.display='inline';" align=top src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif"><img style="DISPLAY: none" id=Codehighlighter1_32_60_Closed_Image onclick="this.style.display='none'; Codehighlighter1_32_60_Closed_Text.style.display='none'; Codehighlighter1_32_60_Open_Image.style.display='inline'; Codehighlighter1_32_60_Open_Text.style.display='inline';" align=top src="http://www.cppblog.com/Images/OutliningIndicators/ContractedBlock.gif"></span><span style="BORDER-BOTTOM: #808080 1px solid; BORDER-LEFT: #808080 1px solid; BACKGROUND-COLOR: #ffffff; DISPLAY: none; BORDER-TOP: #808080 1px solid; BORDER-RIGHT: #808080 1px solid" id=Codehighlighter1_32_60_Closed_Text><img src="http://www.cppblog.com/Images/dot.gif"></span><span id=Codehighlighter1_32_60_Open_Text><span style="COLOR: #000000">{<br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif">&nbsp;&nbsp;&nbsp;&nbsp;printf(&nbsp;</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">%s\n</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">,&nbsp;s&nbsp;);<br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockEnd.gif">}</span></span><span style="COLOR: #000000"><br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/None.gif">call(&nbsp;print_str,&nbsp;</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">a&nbsp;string</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">&nbsp;);</span></div>
<p style="FONT-SIZE: 10pt">&nbsp;</p>
<p style="FONT-SIZE: 10pt">call函数的大致作用，就是调用传递进去的函数print_str，并将参数"a string"传递给目标<br>函数。</p>
<p style="FONT-SIZE: 10pt">但是它是怎么做到的呢？虽然call只有简单的几句汇编代码，但是却包含了很多函数在编译<br>器中的汇编层实现。要了解这段代码的意思，需要知道如下相关知识：</p>
<p style="FONT-SIZE: 10pt">0、函数调用的实现中，编译器通过系统堆栈(ESP寄存器指向）传递参数；<br>1、C语言默认的函数调用规则(_cdecl)中，调用者从右往左将参数压入堆栈，并且调用者负<br>责堆栈平衡，也就是保证调用函数的前后，ESP不变；<br>2、汇编指令call本质上是先将返回地址，通常是该条指令的下一条指令压入堆栈，然后直<br>接跳转到目标位置；<br>3、汇编指令ret则是先从堆栈栈顶取出返回地址，然后跳转过去；<br>4、汇编指令add加上其操作数，貌似占3个字节长度；<br>5、在visual studio中，DEBUG模式下编译器会在我们的代码中插入各种检测代码，而<br>__declspec(naked)则是告诉编译器：别往这里添加代码。</p>
<p style="FONT-SIZE: 10pt">了解了以上常识后，再看这段代码，其本质无非就是利用了这些规则，在代码段跳来跳去。<br>我们来逐步分析一下：</p>
<p style="FONT-SIZE: 10pt">在调用call函数的地方，大概的代码为：</p>
<p style="FONT-SIZE: 10pt">&nbsp;</p>
<div style="BORDER-BOTTOM: #cccccc 1px solid; BORDER-LEFT: #cccccc 1px solid; PADDING-BOTTOM: 4px; BACKGROUND-COLOR: #eeeeee; PADDING-LEFT: 4px; WIDTH: 98%; PADDING-RIGHT: 5px; FONT-SIZE: 13px; WORD-BREAK: break-all; BORDER-TOP: #cccccc 1px solid; BORDER-RIGHT: #cccccc 1px solid; PADDING-TOP: 4px"><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/None.gif"><span style="COLOR: #000000">caller:<br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/None.gif"></span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">&nbsp;堆栈状态，从左往右分别表示栈顶至下<br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/None.gif"></span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">&nbsp;ret_addr是call后的地址，即add&nbsp;esp,&nbsp;8的位置<br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/None.gif"></span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">&nbsp;a1,&nbsp;a2表示函数参数，callee_addr是这里的print_str<br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/None.gif"></span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">&nbsp;stack:&nbsp;ret_addr,&nbsp;callee_addr,&nbsp;a1,&nbsp;a2,&nbsp;<img src="http://www.cppblog.com/Images/dot.gif"></span><span style="COLOR: #008000"><br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/None.gif"></span><span style="COLOR: #000000">call(&nbsp;print_str,&nbsp;</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">a&nbsp;string</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">&nbsp;);&nbsp;<br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/None.gif">add&nbsp;esp,&nbsp;</span><span style="COLOR: #000000">8</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">清除参数传递所占用的堆栈空间，维持堆栈平衡</span><span style="COLOR: #008000"><br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/None.gif"></span><span style="COLOR: #000000">end_label&nbsp;</span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">位于add后的指令，后面会提到</span><span style="COLOR: #008000"><br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/None.gif"></span><span style="COLOR: #000000"><br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/None.gif">call:<br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/None.gif"></span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">&nbsp;此时堆栈stack:&nbsp;ret_addr,&nbsp;a1,&nbsp;a2</span><span style="COLOR: #008000"><br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/None.gif"></span><span style="COLOR: #000000">pop&nbsp;eax&nbsp;</span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">&nbsp;eax&nbsp;=&nbsp;ret_addr;&nbsp;stack:&nbsp;callee_addr,&nbsp;a1,&nbsp;a2,&nbsp;<img src="http://www.cppblog.com/Images/dot.gif"></span><span style="COLOR: #008000"><br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/None.gif"></span><span style="COLOR: #000000">add&nbsp;eax,&nbsp;</span><span style="COLOR: #000000">3</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">&nbsp;eax&nbsp;=&nbsp;end_label;&nbsp;stack:&nbsp;callee_addr,&nbsp;a1,&nbsp;a2,&nbsp;<img src="http://www.cppblog.com/Images/dot.gif"></span><span style="COLOR: #008000"><br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/None.gif"></span><span style="COLOR: #000000">xchg&nbsp;dword&nbsp;ptr[esp],&nbsp;eax&nbsp;</span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">&nbsp;eax&nbsp;=&nbsp;callee_addr;&nbsp;stack:&nbsp;end_label,&nbsp;a1,&nbsp;a2,&nbsp;<img src="http://www.cppblog.com/Images/dot.gif"></span><span style="COLOR: #008000"><br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/None.gif"></span><span style="COLOR: #000000">push&nbsp;eax&nbsp;</span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">&nbsp;stack:&nbsp;callee_addr,&nbsp;end_label,&nbsp;a1,&nbsp;a2,&nbsp;<img src="http://www.cppblog.com/Images/dot.gif"></span><span style="COLOR: #008000"><br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/None.gif"></span><span style="COLOR: #000000">ret&nbsp;</span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">&nbsp;取出callee_addr并跳转，也就跳转到print_str函数的入口，此时堆栈<br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/None.gif">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">&nbsp;stack:&nbsp;end_label,&nbsp;a1,&nbsp;a2,&nbsp;<img src="http://www.cppblog.com/Images/dot.gif"></span><span style="COLOR: #008000"><br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/None.gif"></span><span style="COLOR: #000000"><br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/None.gif">callee(print_str):<br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/None.gif"><img src="http://www.cppblog.com/Images/dot.gif"><br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/None.gif"><img src="http://www.cppblog.com/Images/dot.gif">&nbsp;无视函数内容<br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/None.gif"><img src="http://www.cppblog.com/Images/dot.gif"><br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/None.gif">ret&nbsp;</span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">&nbsp;print_str返回，此时正常情况下，堆栈stack:&nbsp;end_label,&nbsp;a1,&nbsp;a2,&nbsp;<img src="http://www.cppblog.com/Images/dot.gif"><br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/None.gif">&nbsp;</span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">&nbsp;取出end_label并跳转，stack:&nbsp;a1,&nbsp;a2,&nbsp;<img src="http://www.cppblog.com/Images/dot.gif"><br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/None.gif"></span></div>
<p style="FONT-SIZE: 10pt">&nbsp;</p>
<p style="FONT-SIZE: 10pt">那么当callee结束时，则跳转回caller函数中。不过，如过你所见，此时堆栈中还保留着再<br>调用call函数时传入的参数：stack: a1, a2, ...，所以，DEBUG模式下，VS就会提示你堆<br>栈不平衡。这里简单的处理就是手动来进行堆栈平衡：</p>
<p style="FONT-SIZE: 10pt">&nbsp;</p>
<div style="BORDER-BOTTOM: #cccccc 1px solid; BORDER-LEFT: #cccccc 1px solid; PADDING-BOTTOM: 4px; BACKGROUND-COLOR: #eeeeee; PADDING-LEFT: 4px; WIDTH: 98%; PADDING-RIGHT: 5px; FONT-SIZE: 13px; WORD-BREAK: break-all; BORDER-TOP: #cccccc 1px solid; BORDER-RIGHT: #cccccc 1px solid; PADDING-TOP: 4px"><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/None.gif"><span style="COLOR: #000000">&nbsp;&nbsp;&nbsp;&nbsp;call(&nbsp;print_str,&nbsp;</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">a&nbsp;string</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">&nbsp;);<br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/None.gif">&nbsp;&nbsp;&nbsp;&nbsp;__asm<br><img id=Codehighlighter1_49_76_Open_Image onclick="this.style.display='none'; Codehighlighter1_49_76_Open_Text.style.display='none'; Codehighlighter1_49_76_Closed_Image.style.display='inline'; Codehighlighter1_49_76_Closed_Text.style.display='inline';" align=top src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif"><img style="DISPLAY: none" id=Codehighlighter1_49_76_Closed_Image onclick="this.style.display='none'; Codehighlighter1_49_76_Closed_Text.style.display='none'; Codehighlighter1_49_76_Open_Image.style.display='inline'; Codehighlighter1_49_76_Open_Text.style.display='inline';" align=top src="http://www.cppblog.com/Images/OutliningIndicators/ContractedBlock.gif">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="BORDER-BOTTOM: #808080 1px solid; BORDER-LEFT: #808080 1px solid; BACKGROUND-COLOR: #ffffff; DISPLAY: none; BORDER-TOP: #808080 1px solid; BORDER-RIGHT: #808080 1px solid" id=Codehighlighter1_49_76_Closed_Text><img src="http://www.cppblog.com/Images/dot.gif"></span><span id=Codehighlighter1_49_76_Open_Text><span style="COLOR: #000000">{<br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;add&nbsp;esp,&nbsp;</span><span style="COLOR: #000000">4</span><span style="COLOR: #000000">;&nbsp;<br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockEnd.gif">&nbsp;&nbsp;&nbsp;&nbsp;}</span></span></div>
<p style="FONT-SIZE: 10pt">&nbsp;</p>
<p style="FONT-SIZE: 10pt">传入了多少个参数，就得相应地改变esp的值。</p>
<p style="FONT-SIZE: 10pt">话说距离上篇博客都有半年了，自己都不知道时间晃得如此之快。最近<a title=业余折腾了下android开发 href="http://kevinlynx.javaeye.com/">业余折腾了下android开发</a>，<br>一不小心就跨年了。<br>&nbsp;</p>
<img src ="http://www.cppblog.com/kevinlynx/aggbug/137886.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/kevinlynx/" target="_blank">Kevin Lynx</a> 2011-01-02 16:34 <a href="http://www.cppblog.com/kevinlynx/archive/2011/01/02/137886.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>静态库中全局变量的初始化问题</title><link>http://www.cppblog.com/kevinlynx/archive/2010/01/17/105885.html</link><dc:creator>Kevin Lynx</dc:creator><author>Kevin Lynx</author><pubDate>Sun, 17 Jan 2010 11:34:00 GMT</pubDate><guid>http://www.cppblog.com/kevinlynx/archive/2010/01/17/105885.html</guid><wfw:comment>http://www.cppblog.com/kevinlynx/comments/105885.html</wfw:comment><comments>http://www.cppblog.com/kevinlynx/archive/2010/01/17/105885.html#Feedback</comments><slash:comments>19</slash:comments><wfw:commentRss>http://www.cppblog.com/kevinlynx/comments/commentRss/105885.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/kevinlynx/services/trackbacks/105885.html</trackback:ping><description><![CDATA[<p><font size="2"></font>&nbsp; <p><font size="2">在我自己写的一个工厂类实现中，每个产品会注册创建接口到这个工厂类。工厂类使用这些<br>注册进来的创建接口来完成产品的创建。其结构大致如下： </font> <p><font size="2">product *factory::create( long product_type )<br>{<br>&nbsp;&nbsp;&nbsp; creator c = m_creators[product_type];<br>&nbsp;&nbsp;&nbsp; return c();<br>} </font> <p><font size="2">factory::instance().register( PRODUCT_A_TYPE, productA::create );<br>...<br>factory::instance().create( PRODUCT_A_TYPE ); </font> <p><font size="2">这个很普通的工厂实现中，需要写上很多注册代码。每次添加新的产品种类时，也需要修改<br>这些的注册代码。而恰好，这些注册代码可能会被放在一个统一的地方。为了消除这个地方<br>，我使用了偶然间看到的&lt;Modern C++ design&gt;里的做法： </font> <p><font size="2">const bool _local = factory::instance().register( PRODUCT_A_TYPE,... </font> <p><font size="2">也就是说，通过对全局常量_local的自动初始化，来自动完成对该产品的注册。 </font> <p><font size="2">结果，因为这些代码全部被放置于一个静态库。最终的代码文件结构大致为： </font> <p><font size="2">lib<br>&nbsp;&nbsp;&nbsp; - product_a.cpp : 定义了全局常量_local<br>&nbsp;&nbsp;&nbsp; - product_a.h<br>&nbsp;&nbsp;&nbsp; - factory.cpp<br>&nbsp;&nbsp;&nbsp; - factory.h<br>exe<br>&nbsp;&nbsp;&nbsp; - main.cpp </font> <p><font size="2">现在看起来世界很美，因为factory甚至不知道世界上还有个跟上层逻辑相关的product_a。<br>这种模块耦合几乎为0的结构让我窃喜。 </font> <p><font size="2">悲剧的事情首先发生于，开VC调试器，发现打在product_a.cpp里的断点失效。就是那个总<br>是提示说没有为该文件加载调试符号。开始还不在意，以为又是代码和调试符号文件不匹配<br>的原因，折腾了好久，不得其果。 </font> <p><font size="2">后来分析了下，发现这个调试提示，就像我开着调试器打开了一个非本工程的代码文件，而<br>断点就打在这个文件里一样。也就是说，VC把我product_a.cpp当成不是这个工程里的代码<br>文件。 </font> <p><font size="2">按照这个思路写些实验代码，最终发现问题所在：VC链接器根本没链接进product_a.cpp里<br>的代码。表现出来的情况就是，该编译单元里的全局常量（全局变量一样）根本没有得到初<br>始化，因为我跟到factory::register并没有被调用到。为什么VC不链接这个编译单元对应<br>的目标文件？或者说，为什么VC不初始化这个全局常量？ </font> <p><font size="2">原因就在于，product_a.cpp太独立了。一个在整个编译链接阶段都无法确定该文件是否被<br>使用的文件，VC就直接不链接了。相反，当在factory.cpp里写下类似代码： </font> <p><font size="2">void test()<br>{<br>&nbsp;&nbsp;&nbsp; product_a obj;<br>} </font> <p><font size="2">虽然说test函数不会被调用，一切情况也变得正常了。好了，不扯了，给最后我的结论： </font> <p><font size="2">1、如果静态库中某个编译单元在编译阶段被确认为它并没有被外部使用，那么当这个静态<br>库被链接进可执行文件时，链接器忽略掉该编译单元里的代码，那么，链接器自然也不会为<br>该编译单元里出现的全局变量常量生成初始化代码（关于这部分初始化代码可以阅读<br>&lt;linker and loader&gt;一书）；<br>2、上面那条结论存在一种传染性，意思是，当可执行文件里的代码使用到静态库中文件A里<br>的代码，A里又有地方使用到B里的代码，那么B依然会被链接。这种依赖性，应该可以让编<br>译器在编译阶段就发现（显然，上面我举的例子里，factory只有在运行期间才会依赖到<br>product_a.cpp里的代码） </font></p><img src ="http://www.cppblog.com/kevinlynx/aggbug/105885.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/kevinlynx/" target="_blank">Kevin Lynx</a> 2010-01-17 19:34 <a href="http://www.cppblog.com/kevinlynx/archive/2010/01/17/105885.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>自己写内存泄露检测库</title><link>http://www.cppblog.com/kevinlynx/archive/2009/01/23/72514.html</link><dc:creator>Kevin Lynx</dc:creator><author>Kevin Lynx</author><pubDate>Fri, 23 Jan 2009 09:43:00 GMT</pubDate><guid>http://www.cppblog.com/kevinlynx/archive/2009/01/23/72514.html</guid><wfw:comment>http://www.cppblog.com/kevinlynx/comments/72514.html</wfw:comment><comments>http://www.cppblog.com/kevinlynx/archive/2009/01/23/72514.html#Feedback</comments><slash:comments>5</slash:comments><wfw:commentRss>http://www.cppblog.com/kevinlynx/comments/commentRss/72514.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/kevinlynx/services/trackbacks/72514.html</trackback:ping><description><![CDATA[<p><font size=2>author: kevin lynx</font></p>
<p><font size=2>这个内存泄露工具最基本的原理就是利用宏替换掉标准的malloc、free（暂不考虑其他内存分配函数，<br>如realloc、strdup），记录下每次内存分配和释放动作。因为宏的处理发生在预处理阶段，所以可以<br>很容易地用你自己的malloc函数替换掉标准的malloc。例如： </font></p>
<p><font size=2></p>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><img id=Codehighlighter1_0_10_Open_Image onclick="this.style.display='none'; Codehighlighter1_0_10_Open_Text.style.display='none'; Codehighlighter1_0_10_Closed_Image.style.display='inline'; Codehighlighter1_0_10_Closed_Text.style.display='inline';" src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif" align=top><img id=Codehighlighter1_0_10_Closed_Image style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_0_10_Closed_Text.style.display='none'; Codehighlighter1_0_10_Open_Image.style.display='inline'; Codehighlighter1_0_10_Open_Text.style.display='inline';" src="http://www.cppblog.com/Images/OutliningIndicators/ContractedBlock.gif" align=top><span id=Codehighlighter1_0_10_Closed_Text style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff">/**/</span><span id=Codehighlighter1_0_10_Open_Text><span style="COLOR: #008000">/*</span><span style="COLOR: #008000">&nbsp;lib.h&nbsp;</span><span style="COLOR: #008000">*/</span></span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;malloc&nbsp;my_malloc</span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;free&nbsp;my_free&nbsp;</span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><br><img id=Codehighlighter1_60_70_Open_Image onclick="this.style.display='none'; Codehighlighter1_60_70_Open_Text.style.display='none'; Codehighlighter1_60_70_Closed_Image.style.display='inline'; Codehighlighter1_60_70_Closed_Text.style.display='inline';" src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif" align=top><img id=Codehighlighter1_60_70_Closed_Image style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_60_70_Closed_Text.style.display='none'; Codehighlighter1_60_70_Open_Image.style.display='inline'; Codehighlighter1_60_70_Open_Text.style.display='inline';" src="http://www.cppblog.com/Images/OutliningIndicators/ContractedBlock.gif" align=top></span><span id=Codehighlighter1_60_70_Closed_Text style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff">/**/</span><span id=Codehighlighter1_60_70_Open_Text><span style="COLOR: #008000">/*</span><span style="COLOR: #008000">&nbsp;lib.c&nbsp;</span><span style="COLOR: #008000">*/</span></span><span style="COLOR: #000000"><br><img id=Codehighlighter1_72_117_Open_Image onclick="this.style.display='none'; Codehighlighter1_72_117_Open_Text.style.display='none'; Codehighlighter1_72_117_Closed_Image.style.display='inline'; Codehighlighter1_72_117_Closed_Text.style.display='inline';" src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif" align=top><img id=Codehighlighter1_72_117_Closed_Image style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_72_117_Closed_Text.style.display='none'; Codehighlighter1_72_117_Open_Image.style.display='inline'; Codehighlighter1_72_117_Open_Text.style.display='inline';" src="http://www.cppblog.com/Images/OutliningIndicators/ContractedBlock.gif" align=top></span><span id=Codehighlighter1_72_117_Closed_Text style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff">/**/</span><span id=Codehighlighter1_72_117_Open_Text><span style="COLOR: #008000">/*</span><span style="COLOR: #008000">&nbsp;disable&nbsp;these&nbsp;macro&nbsp;in&nbsp;this&nbsp;compile&nbsp;unit&nbsp;</span><span style="COLOR: #008000">*/</span></span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">#undef</span><span style="COLOR: #000000">&nbsp;malloc</span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">#undef</span><span style="COLOR: #000000">&nbsp;free&nbsp;</span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">static</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">&nbsp;count&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">;&nbsp;<br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">my_malloc(&nbsp;size_t&nbsp;size&nbsp;)<br><img id=Codehighlighter1_202_244_Open_Image onclick="this.style.display='none'; Codehighlighter1_202_244_Open_Text.style.display='none'; Codehighlighter1_202_244_Closed_Image.style.display='inline'; Codehighlighter1_202_244_Closed_Text.style.display='inline';" src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif" align=top><img id=Codehighlighter1_202_244_Closed_Image style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_202_244_Closed_Text.style.display='none'; Codehighlighter1_202_244_Open_Image.style.display='inline'; Codehighlighter1_202_244_Open_Text.style.display='inline';" src="http://www.cppblog.com/Images/OutliningIndicators/ContractedBlock.gif" align=top></span><span id=Codehighlighter1_202_244_Closed_Text style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff"><img src="http://www.cppblog.com/Images/dot.gif"></span><span id=Codehighlighter1_202_244_Open_Text><span style="COLOR: #000000">{<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #000000">++</span><span style="COLOR: #000000">count;<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">return</span><span style="COLOR: #000000">&nbsp;malloc(&nbsp;size&nbsp;);<br><img src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockEnd.gif" align=top>}</span></span><span style="COLOR: #000000">&nbsp;<br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000">&nbsp;my_free(&nbsp;</span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">a&nbsp;)<br><img id=Codehighlighter1_272_302_Open_Image onclick="this.style.display='none'; Codehighlighter1_272_302_Open_Text.style.display='none'; Codehighlighter1_272_302_Closed_Image.style.display='inline'; Codehighlighter1_272_302_Closed_Text.style.display='inline';" src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif" align=top><img id=Codehighlighter1_272_302_Closed_Image style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_272_302_Closed_Text.style.display='none'; Codehighlighter1_272_302_Open_Image.style.display='inline'; Codehighlighter1_272_302_Open_Text.style.display='inline';" src="http://www.cppblog.com/Images/OutliningIndicators/ContractedBlock.gif" align=top></span><span id=Codehighlighter1_272_302_Closed_Text style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff"><img src="http://www.cppblog.com/Images/dot.gif"></span><span id=Codehighlighter1_272_302_Open_Text><span style="COLOR: #000000">{<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #000000">--</span><span style="COLOR: #000000">count;<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;free(&nbsp;a&nbsp;);<br><img src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockEnd.gif" align=top>}</span></span><span style="COLOR: #000000">&nbsp;<br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span></div>
<p><br>要使用以上代码，用户在使用时就需要包含lib.h，从而可以使用宏将用户自己写的malloc替换<br>为my_mallo。当程序退出时，如果count大于0，那么可以肯定的是有内存泄露。当然，如果<br>count为负数，则很可能对同一个指针进行多次free。 </font>
<p><font size=2>但是以上代码的功能太局限了。一个真正的内存泄露检测库（工具），至少需要报告泄露的代码<br>文件、函数、行数等信息。当然，如果能报告调用堆栈，就更好了。不过这就依赖于具体的平台，<br>需要使用特定的系统接口才可以获取出。 </font>
<p><font size=2>要实现以上功能也很简单，只需要在每次调用malloc的时候，通过编译器预定义宏__FILE__、<br>__LINE__、__FUNCTION__(__func__)就可以得到文件名、函数、行号等信息。将这些信息保存<br>起来，然后在free的时候移除相应的信息即可。 </font>
<p><font size=2>最简单的实现方式，就是保存一个表，表里记录着每次分配内存的信息： </font>
<p><font size=2>&nbsp;</p>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><span style="COLOR: #0000ff">struct</span><span style="COLOR: #000000">&nbsp;memRecord<br><img id=Codehighlighter1_17_134_Open_Image onclick="this.style.display='none'; Codehighlighter1_17_134_Open_Text.style.display='none'; Codehighlighter1_17_134_Closed_Image.style.display='inline'; Codehighlighter1_17_134_Closed_Text.style.display='inline';" src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif" align=top><img id=Codehighlighter1_17_134_Closed_Image style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_17_134_Closed_Text.style.display='none'; Codehighlighter1_17_134_Open_Image.style.display='inline'; Codehighlighter1_17_134_Open_Text.style.display='inline';" src="http://www.cppblog.com/Images/OutliningIndicators/ContractedBlock.gif" align=top></span><span id=Codehighlighter1_17_134_Closed_Text style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff"><img src="http://www.cppblog.com/Images/dot.gif"></span><span id=Codehighlighter1_17_134_Open_Text><span style="COLOR: #000000">{<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">char</span><span style="COLOR: #000000">&nbsp;file[MAX_FILE_NAME];<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">char</span><span style="COLOR: #000000">&nbsp;func[MAX_FUNC_NAME];<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;size_t&nbsp;lineno;<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">address;<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;size_t&nbsp;size;<br><img src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockEnd.gif" align=top>}</span></span><span style="COLOR: #000000">;&nbsp;<br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">struct</span><span style="COLOR: #000000">&nbsp;memRecord&nbsp;mem_record[MAX_RECORD];&nbsp;<br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span></div>
<p></font>&nbsp;
<p><font size=2>但是，通过单单一个free函数的void*参数，如何获取出对应的分配记录呢？难道：<br></p>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><span style="COLOR: #0000ff">for</span><span style="COLOR: #000000">(&nbsp;size_t&nbsp;i&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">;&nbsp;i&nbsp;</span><span style="COLOR: #000000">&lt;</span><span style="COLOR: #000000">&nbsp;MAX_RECORD;&nbsp;</span><span style="COLOR: #000000">++</span><span style="COLOR: #000000">&nbsp;i&nbsp;)<br><img id=Codehighlighter1_42_119_Open_Image onclick="this.style.display='none'; Codehighlighter1_42_119_Open_Text.style.display='none'; Codehighlighter1_42_119_Closed_Image.style.display='inline'; Codehighlighter1_42_119_Closed_Text.style.display='inline';" src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif" align=top><img id=Codehighlighter1_42_119_Closed_Image style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_42_119_Closed_Text.style.display='none'; Codehighlighter1_42_119_Open_Image.style.display='inline'; Codehighlighter1_42_119_Open_Text.style.display='inline';" src="http://www.cppblog.com/Images/OutliningIndicators/ContractedBlock.gif" align=top></span><span id=Codehighlighter1_42_119_Closed_Text style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff"><img src="http://www.cppblog.com/Images/dot.gif"></span><span id=Codehighlighter1_42_119_Open_Text><span style="COLOR: #000000">{<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">if</span><span style="COLOR: #000000">(&nbsp;address&nbsp;</span><span style="COLOR: #000000">==</span><span style="COLOR: #000000">&nbsp;mem_record[i].address&nbsp;)&nbsp;<br><img id=Codehighlighter1_92_117_Open_Image onclick="this.style.display='none'; Codehighlighter1_92_117_Open_Text.style.display='none'; Codehighlighter1_92_117_Closed_Image.style.display='inline'; Codehighlighter1_92_117_Closed_Text.style.display='inline';" src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif" align=top><img id=Codehighlighter1_92_117_Closed_Image style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_92_117_Closed_Text.style.display='none'; Codehighlighter1_92_117_Open_Image.style.display='inline'; Codehighlighter1_92_117_Open_Text.style.display='inline';" src="http://www.cppblog.com/Images/OutliningIndicators/ContractedSubBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;</span><span id=Codehighlighter1_92_117_Closed_Text style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff"><img src="http://www.cppblog.com/Images/dot.gif"></span><span id=Codehighlighter1_92_117_Open_Text><span style="COLOR: #000000">{<br><img id=Codehighlighter1_102_111_Open_Image onclick="this.style.display='none'; Codehighlighter1_102_111_Open_Text.style.display='none'; Codehighlighter1_102_111_Closed_Image.style.display='inline'; Codehighlighter1_102_111_Closed_Text.style.display='inline';" src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif" align=top><img id=Codehighlighter1_102_111_Closed_Image style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_102_111_Closed_Text.style.display='none'; Codehighlighter1_102_111_Open_Image.style.display='inline'; Codehighlighter1_102_111_Open_Text.style.display='inline';" src="http://www.cppblog.com/Images/OutliningIndicators/ContractedSubBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span id=Codehighlighter1_102_111_Closed_Text style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff">/**/</span><span id=Codehighlighter1_102_111_Open_Text><span style="COLOR: #008000">/*</span><span style="COLOR: #008000">&nbsp;shit&nbsp;</span><span style="COLOR: #008000">*/</span></span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;}</span></span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockEnd.gif" align=top>}</span></span><span style="COLOR: #000000">&nbsp;</span></div>
<p></font>&nbsp;
<p><font size=2>虽然可行，但是很stupid。这里提供一个小技巧： </font>
<p><font size=2>&nbsp;</p>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">my_malloc(&nbsp;size_t&nbsp;size&nbsp;)<br><img id=Codehighlighter1_31_122_Open_Image onclick="this.style.display='none'; Codehighlighter1_31_122_Open_Text.style.display='none'; Codehighlighter1_31_122_Closed_Image.style.display='inline'; Codehighlighter1_31_122_Closed_Text.style.display='inline';" src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif" align=top><img id=Codehighlighter1_31_122_Closed_Image style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_31_122_Closed_Text.style.display='none'; Codehighlighter1_31_122_Open_Image.style.display='inline'; Codehighlighter1_31_122_Open_Text.style.display='inline';" src="http://www.cppblog.com/Images/OutliningIndicators/ContractedBlock.gif" align=top></span><span id=Codehighlighter1_31_122_Closed_Text style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff"><img src="http://www.cppblog.com/Images/dot.gif"></span><span id=Codehighlighter1_31_122_Open_Text><span style="COLOR: #000000">{<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">a&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;malloc(&nbsp;size&nbsp;</span><span style="COLOR: #000000">+</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #0000ff">sizeof</span><span style="COLOR: #000000">(&nbsp;size_t&nbsp;)&nbsp;);<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">return</span><span style="COLOR: #000000">&nbsp;(</span><span style="COLOR: #0000ff">char</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">)a&nbsp;</span><span style="COLOR: #000000">+</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #0000ff">sizeof</span><span style="COLOR: #000000">(&nbsp;size_t&nbsp;);<br><img src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockEnd.gif" align=top>}</span></span><span style="COLOR: #000000">&nbsp;<br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000">&nbsp;my_free(&nbsp;</span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">a&nbsp;)<br><img id=Codehighlighter1_150_265_Open_Image onclick="this.style.display='none'; Codehighlighter1_150_265_Open_Text.style.display='none'; Codehighlighter1_150_265_Closed_Image.style.display='inline'; Codehighlighter1_150_265_Closed_Text.style.display='inline';" src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif" align=top><img id=Codehighlighter1_150_265_Closed_Image style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_150_265_Closed_Text.style.display='none'; Codehighlighter1_150_265_Open_Image.style.display='inline'; Codehighlighter1_150_265_Open_Text.style.display='inline';" src="http://www.cppblog.com/Images/OutliningIndicators/ContractedBlock.gif" align=top></span><span id=Codehighlighter1_150_265_Closed_Text style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff"><img src="http://www.cppblog.com/Images/dot.gif"></span><span id=Codehighlighter1_150_265_Open_Text><span style="COLOR: #000000">{<br><img id=Codehighlighter1_156_198_Open_Image onclick="this.style.display='none'; Codehighlighter1_156_198_Open_Text.style.display='none'; Codehighlighter1_156_198_Closed_Image.style.display='inline'; Codehighlighter1_156_198_Closed_Text.style.display='inline';" src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif" align=top><img id=Codehighlighter1_156_198_Closed_Image style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_156_198_Closed_Text.style.display='none'; Codehighlighter1_156_198_Open_Image.style.display='inline'; Codehighlighter1_156_198_Open_Text.style.display='inline';" src="http://www.cppblog.com/Images/OutliningIndicators/ContractedSubBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;</span><span id=Codehighlighter1_156_198_Closed_Text style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff">/**/</span><span id=Codehighlighter1_156_198_Open_Text><span style="COLOR: #008000">/*</span><span style="COLOR: #008000">&nbsp;actually,&nbsp;'a'&nbsp;is&nbsp;not&nbsp;the&nbsp;real&nbsp;address&nbsp;</span><span style="COLOR: #008000">*/</span></span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">char</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">p&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;((</span><span style="COLOR: #0000ff">char</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">)a&nbsp;</span><span style="COLOR: #000000">-</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #0000ff">sizeof</span><span style="COLOR: #000000">(&nbsp;size_t&nbsp;)&nbsp;);&nbsp;&nbsp;&nbsp;&nbsp;<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;free(&nbsp;p&nbsp;);<br><img src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockEnd.gif" align=top>}</span></span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span></div>
<p>&nbsp;</font>
<p><font size=2>意思就是说，我多分配了4字节内存（sizeof( size_t ) ），用于保存这次分配记录在mem_record<br>中被保存的索引。在释放内存的时候，通过一些地址偏移计算，就可以获取出真正的系统malloc<br>返回的地址，从而安全释放（别给我说这里的计算存在平台和编译器的限制，没认真看文章的SB才说）。 </font>
<p><font size=2>另一个问题是，我们如何处理每次分配释放时，对于分配记录那个数据结构，也就是mem_record。<br>每一次free的时候，移除的记录可能位于mem_record的任何位置。一定时间后，mem_record内部<br>将出现很多漏洞（已经没用的数组位置）。解决这个问题最直接当然还是最stupid的方法，就是每次<br>free移除记录时重新整理一遍mem_record。如果你这样做了，那你的malloc/free比微软的还慢。 </font>
<p><font size=2>我的解决方法是：<br>size_t free_index[MAX_RECORD];<br>用于保存这些出现漏洞的index。每一次free移除记录时，就把这个记录对应的inex保存进来，表示<br>这个index指向的mem_record[]可用。每一次malloc的时候，先从这里取index，如果这里没有，那<br>可以直接从mem_record的末尾取。 </font>
<p><font size=2>具体细节就不阐述了，典型的空间换时间方法。整个库很简单，代码100来行。我也只进行过粗略的<br>测试。我肯定这100来行代码是有问题的，相信自己的代码存在问题是对bug的一种觉悟，哈哈哈。 </font>
<p><font size=2>这个东西只检测C语言的内存泄露，其实要检测C++的也很简单，只需要重载new和delete就可以了。 </font>
<p><font size=2>要放春节假了，在公司的最后几个小时实在无聊，才做了这个东西，前后花了1个多小时，写起来感觉<br>不错。</font></p>
<p><font size=2></font>&nbsp;</p>
<p><font size=2></font>&nbsp;</p>
<a href="http://www.cppblog.com/Files/kevinlynx/cmlc.zip" target=_blank>代码下载</a>
<p><font size=2></font>&nbsp;</p>
<p><font size=2></font></p>
<img src ="http://www.cppblog.com/kevinlynx/aggbug/72514.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/kevinlynx/" target="_blank">Kevin Lynx</a> 2009-01-23 17:43 <a href="http://www.cppblog.com/kevinlynx/archive/2009/01/23/72514.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>最近的两个问题：less for std::map，静态变量初始化顺序</title><link>http://www.cppblog.com/kevinlynx/archive/2008/11/11/66623.html</link><dc:creator>Kevin Lynx</dc:creator><author>Kevin Lynx</author><pubDate>Tue, 11 Nov 2008 09:55:00 GMT</pubDate><guid>http://www.cppblog.com/kevinlynx/archive/2008/11/11/66623.html</guid><wfw:comment>http://www.cppblog.com/kevinlynx/comments/66623.html</wfw:comment><comments>http://www.cppblog.com/kevinlynx/archive/2008/11/11/66623.html#Feedback</comments><slash:comments>13</slash:comments><wfw:commentRss>http://www.cppblog.com/kevinlynx/comments/commentRss/66623.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/kevinlynx/services/trackbacks/66623.html</trackback:ping><description><![CDATA[<p style="FONT-SIZE: 10pt"><br>说下最近自己遇到的两个值得让人注意的问题。<br>其一是关于自己给std::map写less predicate，std::map第三个参数是一个典型的functor。map内部将使用<br>这个functor去判定两个元素是否相等，默认使用的是std::less。但是为什么传入的是一个判断第一个参数<br>小于第二个参数的functor，而不是一个判断两个参数是否相等的functor？按照STL文档的说法，当检查两<br>个参数没有小于也没有大于的关系时，就表示两个参数相等。不管怎样，我遇到了需要自己写这个functor<br>的需求，于是：</p>
<p style="FONT-SIZE: 10pt">struct MyLess<br>{<br>&nbsp;&nbsp;&nbsp; bool operator() ( long left, long right )<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //...<br>&nbsp;&nbsp;&nbsp; }<br>};</p>
<p style="FONT-SIZE: 10pt">DEBUG模式下编译没问题，RELEASE模式下却出现C3848的错误。这就有点奇怪了，如果确实存在语法错误，<br>那么DEBUG和RELASE应该一样才对。查了下MSDN，C3848的错误是因为const限定符造成的，如：</p>
<p style="FONT-SIZE: 10pt">const MyLess pr;<br>pr(); // C3848</p>
<p style="FONT-SIZE: 10pt">于是，在operator()后加上const，就OK了。看了下VC std::map相关代码，以为是DEBUG和RELEASE使用了不<br>同的代码造成。但是我始终没找到不同点。另一方面，就STL内部的风格来看，应该不会把predicator处理<br>成const &amp;之类的东西，全部是value形式的。奇怪了。</p>
<p style="FONT-SIZE: 10pt">第二个问题，涉及到静态变量。这个东西给我的印象特别深刻，因为以前去一家外企应聘时被问到，当时<br>觉得那个LEADER特别厉害。回来后让我反思，是不是过多地关注了C++里的花哨，而漏掉了C里的朴素？导致<br>我至今对纯C存在偏好。</p>
<p style="FONT-SIZE: 10pt">说正题，我现在有如下的文件关系：</p>
<p style="FONT-SIZE: 10pt">// def.h<br>struct Obj<br>{<br>&nbsp;&nbsp;&nbsp; Obj()<br>&nbsp;{<br>&nbsp;&nbsp;ObjMgr::AddObj( id, this );<br>&nbsp;}<br>&nbsp;int id;<br>};</p>
<p style="FONT-SIZE: 10pt">struct ObjMgr<br>{<br>&nbsp;&nbsp;&nbsp; static void AddObj( int id, Obj *t )<br>&nbsp;{<br>&nbsp;&nbsp;ObjTable[id] = t;<br>&nbsp;}<br>&nbsp;static std::map&lt;int, Obj*&gt; ObjTable;<br>};</p>
<p style="FONT-SIZE: 10pt">static Obj _t;</p>
<p style="FONT-SIZE: 10pt">// ObjMgr.cpp<br>#include "def.h"</p>
<p style="FONT-SIZE: 10pt">static std::map&lt;int, Obj*&gt;::ObjMgr ObjTable;</p>
<p style="FONT-SIZE: 10pt">// main.cpp<br>#include "def.h"</p>
<p style="FONT-SIZE: 10pt">这里举的例子可能有点不恰当，我在一台没有编译器的机器上写的这篇文章。忽略掉这些旁支末节。我的意思，<br>就是想让Obj自己自动向ObjMgr里添加自己。我们都知道静态变量将在程序启动时被初始化，先于main执行之前。</p>
<p style="FONT-SIZE: 10pt">上面代码有两个问题：</p>
<p style="FONT-SIZE: 10pt">一、<br>代码没有按照我预期地执行，如果你按照我的意思做个测试，你的程序甚至在进main之前就crash了。我假定你<br>用的是VC，因为我没在其他编译器上试验过。问题就在于，Obj的构造依赖于ObjTable这个map对象。在调试过程<br>中我发现，虽然ObjTable拥有了内存空间，其this指针有效，但是，map对象并没有得到构造。我的意思是，Obj<br>的构造先于ObjTable构造（下几个断点即可轻易发现），那么在执行map::operator[]时，就出错了，因为这个时候<br>map里相关数据还没准备好。</p>
<p style="FONT-SIZE: 10pt">那是否存在某种机制可以手动静态变量的初始化顺序呢？不知道。我最后怎样解决这个问题的？</p>
<p style="FONT-SIZE: 10pt">二、<br>在还没有想到解决办法之前（不改变我的设计），我发现了这段代码的另一个问题：我在头文件里定义了静态<br>变量：static Obj _t; 这有什么问题？想想预编译这个过程即可知道，头文件在预编译阶段被文本展开到CPP<br>文件里，然后，ObjMgr.cpp和main.cpp文件里都将出现static Obj _t;代码。也就是说，ObjMgr.obj和main.obj<br>里都有一个文件静态变量_t。</p>
<p style="FONT-SIZE: 10pt">看来，在头文件里放这个静态变量是肯定不对的。于是，我将_t移动到ObjMgr.cpp里：</p>
<p style="FONT-SIZE: 10pt">// ObjMgr.cpp<br>#include "def.h"</p>
<p style="FONT-SIZE: 10pt">static std::map&lt;int, Obj*&gt;::ObjMgr ObjTable;<br>static Obj _t;</p>
<p style="FONT-SIZE: 10pt">按照这样的顺序定义后，_t的构造居然晚于ObjTable了。也就是说，放置于前面的变量定义，就意味着它将被<br>首先构造初始化。这样两个问题都解决了。</p>
<p style="FONT-SIZE: 10pt">但是，谁能保证这一点特性？C标准文档里？还是VC编译器自己？</p>
<p style="FONT-SIZE: 10pt">&nbsp;</p>
<p style="FONT-SIZE: 10pt">&nbsp;</p>
<p style="FONT-SIZE: 10pt">&nbsp;</p>
<p style="FONT-SIZE: 10pt"><br>&nbsp;</p>
<img src ="http://www.cppblog.com/kevinlynx/aggbug/66623.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/kevinlynx/" target="_blank">Kevin Lynx</a> 2008-11-11 17:55 <a href="http://www.cppblog.com/kevinlynx/archive/2008/11/11/66623.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>让人无语的boost</title><link>http://www.cppblog.com/kevinlynx/archive/2008/10/15/64013.html</link><dc:creator>Kevin Lynx</dc:creator><author>Kevin Lynx</author><pubDate>Wed, 15 Oct 2008 03:23:00 GMT</pubDate><guid>http://www.cppblog.com/kevinlynx/archive/2008/10/15/64013.html</guid><wfw:comment>http://www.cppblog.com/kevinlynx/comments/64013.html</wfw:comment><comments>http://www.cppblog.com/kevinlynx/archive/2008/10/15/64013.html#Feedback</comments><slash:comments>10</slash:comments><wfw:commentRss>http://www.cppblog.com/kevinlynx/comments/commentRss/64013.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/kevinlynx/services/trackbacks/64013.html</trackback:ping><description><![CDATA[<p><font size=2>&nbsp;&nbsp;&nbsp; 关于BOOST，撞车，严重撞车。每一次都让我有点无语。<br>&nbsp;&nbsp;&nbsp; 第一次是我所谓的宏递归，其实就是一个macro library，有一天就不小心在BOOST的library list上<br>看到了这个东西。当然，BOOST很牛，别人的这个macro是真的library。但是，我们的需求撞车，我们的<br>实现手段撞车。于是下定决心下次想要实现个什么东西的时候，先去看看BOOST，可以省掉不少脑力。<br>&nbsp;&nbsp;&nbsp; 本来就没有做好，何必吃力不讨好？<br>&nbsp;&nbsp;&nbsp; 第二次，当我写下类似的模板代码时：<br></p>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><span style="COLOR: #000000">&nbsp;&nbsp;&nbsp;&nbsp;template&nbsp;</span><span style="COLOR: #000000">&lt;</span><span style="COLOR: #000000">typename&nbsp;_Tp</span><span style="COLOR: #000000">&gt;</span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000">&nbsp;func(&nbsp;_Tp&nbsp;t&nbsp;);</span></div>
<p><br>&nbsp;&nbsp;&nbsp; 我总要花掉几秒钟时间去决策func的参数是用_Tp&amp;还是_Tp，也就是所谓的究竟是按值传送还是按引用<br>传送。如果按值传送，当_Tp为一个类时，复制的东西过多时，显然效率上过不去。作为func的实现者，良<br>心上更过不去。后来一想，STL的各种算法里到处都是按值传送，这样做总有它的理由吧？<br>&nbsp;&nbsp;&nbsp; 但是，这样做就是不够完美。<br>&nbsp;&nbsp;&nbsp; 于是想起了boost::ref。但是这个时候我并不知道boost::ref是个什么东西。我只是以前在各种地方<br>看到过这个东西。我还是决定自己实现一个。<br>&nbsp;&nbsp;&nbsp; 实现一个什么？考虑有：<br></p>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><span style="COLOR: #000000">&nbsp;&nbsp;&nbsp;&nbsp;template&nbsp;</span><span style="COLOR: #000000">&lt;</span><span style="COLOR: #000000">typename&nbsp;_Tp</span><span style="COLOR: #000000">&gt;</span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000">&nbsp;func(&nbsp;_Tp&nbsp;t&nbsp;);</span></div>
<p><br>&nbsp;&nbsp;&nbsp; 而我这个时候要传递一个类对象过去CBaseObject obj。为了效率，我写下如下的代码：<br></p>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><span style="COLOR: #000000">&nbsp;&nbsp;&nbsp;&nbsp;template&nbsp;</span><span style="COLOR: #000000">&lt;</span><span style="COLOR: #000000">typename&nbsp;_Tp</span><span style="COLOR: #000000">&gt;</span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">class</span><span style="COLOR: #000000">&nbsp;ref_wrapper<br><img id=Codehighlighter1_54_198_Open_Image onclick="this.style.display='none'; Codehighlighter1_54_198_Open_Text.style.display='none'; Codehighlighter1_54_198_Closed_Image.style.display='inline'; Codehighlighter1_54_198_Closed_Text.style.display='inline';" src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif" align=top><img id=Codehighlighter1_54_198_Closed_Image style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_54_198_Closed_Text.style.display='none'; Codehighlighter1_54_198_Open_Image.style.display='inline'; Codehighlighter1_54_198_Open_Text.style.display='inline';" src="http://www.cppblog.com/Images/OutliningIndicators/ContractedBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;</span><span id=Codehighlighter1_54_198_Closed_Text style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff"><img src="http://www.cppblog.com/Images/dot.gif"></span><span id=Codehighlighter1_54_198_Open_Text><span style="COLOR: #000000">{<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">public</span><span style="COLOR: #000000">:<br><img id=Codehighlighter1_111_113_Open_Image onclick="this.style.display='none'; Codehighlighter1_111_113_Open_Text.style.display='none'; Codehighlighter1_111_113_Closed_Image.style.display='inline'; Codehighlighter1_111_113_Closed_Text.style.display='inline';" src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif" align=top><img id=Codehighlighter1_111_113_Closed_Image style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_111_113_Closed_Text.style.display='none'; Codehighlighter1_111_113_Open_Image.style.display='inline'; Codehighlighter1_111_113_Open_Text.style.display='inline';" src="http://www.cppblog.com/Images/OutliningIndicators/ContractedSubBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ref_wrapper(&nbsp;_Tp&nbsp;</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">p&nbsp;)&nbsp;:&nbsp;_obj(&nbsp;</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">p&nbsp;)&nbsp;</span><span id=Codehighlighter1_111_113_Closed_Text style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff"><img src="http://www.cppblog.com/Images/dot.gif"></span><span id=Codehighlighter1_111_113_Open_Text><span style="COLOR: #000000">{&nbsp;}</span></span><span style="COLOR: #000000"><br><img id=Codehighlighter1_140_156_Open_Image onclick="this.style.display='none'; Codehighlighter1_140_156_Open_Text.style.display='none'; Codehighlighter1_140_156_Closed_Image.style.display='inline'; Codehighlighter1_140_156_Closed_Text.style.display='inline';" src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif" align=top><img id=Codehighlighter1_140_156_Closed_Image style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_140_156_Closed_Text.style.display='none'; Codehighlighter1_140_156_Open_Image.style.display='inline'; Codehighlighter1_140_156_Open_Text.style.display='inline';" src="http://www.cppblog.com/Images/OutliningIndicators/ContractedSubBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">operator</span><span style="COLOR: #000000">&nbsp;_Tp</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">&nbsp;()&nbsp;</span><span id=Codehighlighter1_140_156_Closed_Text style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff"><img src="http://www.cppblog.com/Images/dot.gif"></span><span id=Codehighlighter1_140_156_Open_Text><span style="COLOR: #000000">{&nbsp;</span><span style="COLOR: #0000ff">return</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">_obj;&nbsp;}</span></span><span style="COLOR: #000000">&nbsp;&nbsp;&nbsp;&nbsp;<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">private</span><span style="COLOR: #000000">:<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_Tp&nbsp;</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">_obj;<br><img src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockEnd.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;}</span></span><span style="COLOR: #000000">;<br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span></div>
<p><br>&nbsp;&nbsp;&nbsp; 然后再使用func时，func( ref_wrapper&lt;CBaseObject&gt;( obj ) );这样，发生复制操作的最多就是这<br>个ref_wrapper，基本上也就是复制了一个指针，而不会复制整个obj。当然，这里可以写一个模板函数去<br>简化func的调用，如：<br></p>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><span style="COLOR: #000000">&nbsp;&nbsp;&nbsp;&nbsp;template&nbsp;</span><span style="COLOR: #000000">&lt;</span><span style="COLOR: #000000">typename&nbsp;_Tp</span><span style="COLOR: #000000">&gt;</span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;ref_wrapper</span><span style="COLOR: #000000">&lt;</span><span style="COLOR: #000000">_Tp</span><span style="COLOR: #000000">&gt;</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #0000ff">ref</span><span style="COLOR: #000000">(&nbsp;_Tp&nbsp;</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">t&nbsp;)<br><img id=Codehighlighter1_67_111_Open_Image onclick="this.style.display='none'; Codehighlighter1_67_111_Open_Text.style.display='none'; Codehighlighter1_67_111_Closed_Image.style.display='inline'; Codehighlighter1_67_111_Closed_Text.style.display='inline';" src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif" align=top><img id=Codehighlighter1_67_111_Closed_Image style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_67_111_Closed_Text.style.display='none'; Codehighlighter1_67_111_Open_Image.style.display='inline'; Codehighlighter1_67_111_Open_Text.style.display='inline';" src="http://www.cppblog.com/Images/OutliningIndicators/ContractedBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;</span><span id=Codehighlighter1_67_111_Closed_Text style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff"><img src="http://www.cppblog.com/Images/dot.gif"></span><span id=Codehighlighter1_67_111_Open_Text><span style="COLOR: #000000">{<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">return</span><span style="COLOR: #000000">&nbsp;ref_wrapper</span><span style="COLOR: #000000">&lt;</span><span style="COLOR: #000000">_Tp</span><span style="COLOR: #000000">&gt;</span><span style="COLOR: #000000">(&nbsp;t&nbsp;);<br><img src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockEnd.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;}</span></span></div>
<p><br>&nbsp;&nbsp;&nbsp; 这样调用的时候就简单了：func( ref( obj ) );<br>&nbsp;&nbsp;&nbsp; 其实这就是boost的ref库，按照其官方文档，ref库就是：<br>&nbsp;&nbsp;&nbsp; The Ref library is a small library that is useful for passing references to function <br>templates (algorithms) that would usually take copies of their arguments. </font>
<p><font size=2>&nbsp;&nbsp;&nbsp; 然后我就懵了。于是我不得不在kl_ref.h里写上check out boost::ref for more information的字眼。 </font>
<p><font size=2>&nbsp;&nbsp;&nbsp; 好，接下来说说第三次。<br>&nbsp;&nbsp;&nbsp; 第三次我遇到了这样一种需求，我需要一个容器，就像你知道的std::list。但是与std::list甚至STL<br>中所有容器都不同的是，这个容器里保存的东西具有不同的类型。<br>&nbsp;&nbsp;&nbsp; 这个时候我想起了tuple。我没有实现过tuple。大致上这个东西的实现原理就是利用类型递归来保存<br>数据，就像loki的type list。另一方面，tuple的尺寸似乎不能动态增长。<br>&nbsp;&nbsp;&nbsp; 于是我有了自己撇脚的实现：<br></font>
<p>&nbsp;</p>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><span style="COLOR: #000000">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">class</span><span style="COLOR: #000000">&nbsp;base_type<br><img id=Codehighlighter1_24_63_Open_Image onclick="this.style.display='none'; Codehighlighter1_24_63_Open_Text.style.display='none'; Codehighlighter1_24_63_Closed_Image.style.display='inline'; Codehighlighter1_24_63_Closed_Text.style.display='inline';" src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif" align=top><img id=Codehighlighter1_24_63_Closed_Image style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_24_63_Closed_Text.style.display='none'; Codehighlighter1_24_63_Open_Image.style.display='inline'; Codehighlighter1_24_63_Open_Text.style.display='inline';" src="http://www.cppblog.com/Images/OutliningIndicators/ContractedBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;</span><span id=Codehighlighter1_24_63_Closed_Text style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff"><img src="http://www.cppblog.com/Images/dot.gif"></span><span id=Codehighlighter1_24_63_Open_Text><span style="COLOR: #000000">{<br><img id=Codehighlighter1_55_57_Open_Image onclick="this.style.display='none'; Codehighlighter1_55_57_Open_Text.style.display='none'; Codehighlighter1_55_57_Closed_Image.style.display='inline'; Codehighlighter1_55_57_Closed_Text.style.display='inline';" src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif" align=top><img id=Codehighlighter1_55_57_Closed_Image style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_55_57_Closed_Text.style.display='none'; Codehighlighter1_55_57_Open_Image.style.display='inline'; Codehighlighter1_55_57_Open_Text.style.display='inline';" src="http://www.cppblog.com/Images/OutliningIndicators/ContractedSubBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">virtual</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">~</span><span style="COLOR: #000000">base_type()&nbsp;</span><span id=Codehighlighter1_55_57_Closed_Text style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff"><img src="http://www.cppblog.com/Images/dot.gif"></span><span id=Codehighlighter1_55_57_Open_Text><span style="COLOR: #000000">{&nbsp;}</span></span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockEnd.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;}</span></span><span style="COLOR: #000000">;<br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;template&nbsp;</span><span style="COLOR: #000000">&lt;</span><span style="COLOR: #000000">typename&nbsp;_Tp</span><span style="COLOR: #000000">&gt;</span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">class</span><span style="COLOR: #000000">&nbsp;var_wrapper<br><img id=Codehighlighter1_120_257_Open_Image onclick="this.style.display='none'; Codehighlighter1_120_257_Open_Text.style.display='none'; Codehighlighter1_120_257_Closed_Image.style.display='inline'; Codehighlighter1_120_257_Closed_Text.style.display='inline';" src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif" align=top><img id=Codehighlighter1_120_257_Closed_Image style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_120_257_Closed_Text.style.display='none'; Codehighlighter1_120_257_Open_Image.style.display='inline'; Codehighlighter1_120_257_Open_Text.style.display='inline';" src="http://www.cppblog.com/Images/OutliningIndicators/ContractedBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;</span><span id=Codehighlighter1_120_257_Closed_Text style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff"><img src="http://www.cppblog.com/Images/dot.gif"></span><span id=Codehighlighter1_120_257_Open_Text><span style="COLOR: #000000">{<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">public</span><span style="COLOR: #000000">:<br><img id=Codehighlighter1_181_182_Open_Image onclick="this.style.display='none'; Codehighlighter1_181_182_Open_Text.style.display='none'; Codehighlighter1_181_182_Closed_Image.style.display='inline'; Codehighlighter1_181_182_Closed_Text.style.display='inline';" src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif" align=top><img id=Codehighlighter1_181_182_Closed_Image style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_181_182_Closed_Text.style.display='none'; Codehighlighter1_181_182_Open_Image.style.display='inline'; Codehighlighter1_181_182_Open_Text.style.display='inline';" src="http://www.cppblog.com/Images/OutliningIndicators/ContractedSubBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;var_wrapper(&nbsp;</span><span style="COLOR: #0000ff">const</span><span style="COLOR: #000000">&nbsp;_Tp&nbsp;</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">t&nbsp;)&nbsp;:&nbsp;_t(&nbsp;t&nbsp;)&nbsp;&nbsp;</span><span id=Codehighlighter1_181_182_Closed_Text style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff"><img src="http://www.cppblog.com/Images/dot.gif"></span><span id=Codehighlighter1_181_182_Open_Text><span style="COLOR: #000000">{}</span></span><span style="COLOR: #000000"><br><img id=Codehighlighter1_209_222_Open_Image onclick="this.style.display='none'; Codehighlighter1_209_222_Open_Text.style.display='none'; Codehighlighter1_209_222_Closed_Image.style.display='inline'; Codehighlighter1_209_222_Closed_Text.style.display='inline';" src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif" align=top><img id=Codehighlighter1_209_222_Closed_Image style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_209_222_Closed_Text.style.display='none'; Codehighlighter1_209_222_Open_Image.style.display='inline'; Codehighlighter1_209_222_Open_Text.style.display='inline';" src="http://www.cppblog.com/Images/OutliningIndicators/ContractedSubBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">operator</span><span style="COLOR: #000000">&nbsp;_Tp</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">&nbsp;()&nbsp;</span><span id=Codehighlighter1_209_222_Closed_Text style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff"><img src="http://www.cppblog.com/Images/dot.gif"></span><span id=Codehighlighter1_209_222_Open_Text><span style="COLOR: #000000">{&nbsp;</span><span style="COLOR: #0000ff">return</span><span style="COLOR: #000000">&nbsp;_t;&nbsp;}</span></span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">private</span><span style="COLOR: #000000">:<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_Tp&nbsp;_t;<br><img src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockEnd.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;}</span></span><span style="COLOR: #000000">;&nbsp;&nbsp;&nbsp;<br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">class</span><span style="COLOR: #000000">&nbsp;var_list<br><img id=Codehighlighter1_287_866_Open_Image onclick="this.style.display='none'; Codehighlighter1_287_866_Open_Text.style.display='none'; Codehighlighter1_287_866_Closed_Image.style.display='inline'; Codehighlighter1_287_866_Closed_Text.style.display='inline';" src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif" align=top><img id=Codehighlighter1_287_866_Closed_Image style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_287_866_Closed_Text.style.display='none'; Codehighlighter1_287_866_Open_Image.style.display='inline'; Codehighlighter1_287_866_Open_Text.style.display='inline';" src="http://www.cppblog.com/Images/OutliningIndicators/ContractedBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;</span><span id=Codehighlighter1_287_866_Closed_Text style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff"><img src="http://www.cppblog.com/Images/dot.gif"></span><span id=Codehighlighter1_287_866_Open_Text><span style="COLOR: #000000">{<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">public</span><span style="COLOR: #000000">:<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;typedef&nbsp;std::vector</span><span style="COLOR: #000000">&lt;</span><span style="COLOR: #000000">base_type</span><span style="COLOR: #000000">*&gt;</span><span style="COLOR: #000000">&nbsp;TypeList;<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">public</span><span style="COLOR: #000000">:<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<img src="http://www.cppblog.com/Images/dot.gif"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;template&nbsp;</span><span style="COLOR: #000000">&lt;</span><span style="COLOR: #000000">typename&nbsp;_Tp</span><span style="COLOR: #000000">&gt;</span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000">&nbsp;add(&nbsp;</span><span style="COLOR: #0000ff">const</span><span style="COLOR: #000000">&nbsp;_Tp&nbsp;</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">t&nbsp;)<br><img id=Codehighlighter1_448_555_Open_Image onclick="this.style.display='none'; Codehighlighter1_448_555_Open_Text.style.display='none'; Codehighlighter1_448_555_Closed_Image.style.display='inline'; Codehighlighter1_448_555_Closed_Text.style.display='inline';" src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif" align=top><img id=Codehighlighter1_448_555_Closed_Image style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_448_555_Closed_Text.style.display='none'; Codehighlighter1_448_555_Open_Image.style.display='inline'; Codehighlighter1_448_555_Open_Text.style.display='inline';" src="http://www.cppblog.com/Images/OutliningIndicators/ContractedSubBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span id=Codehighlighter1_448_555_Closed_Text style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff"><img src="http://www.cppblog.com/Images/dot.gif"></span><span id=Codehighlighter1_448_555_Open_Text><span style="COLOR: #000000">{<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;var_wrapper</span><span style="COLOR: #000000">&lt;</span><span style="COLOR: #000000">_Tp</span><span style="COLOR: #000000">&gt;</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">var&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #0000ff">new</span><span style="COLOR: #000000">&nbsp;var_wrapper</span><span style="COLOR: #000000">&lt;</span><span style="COLOR: #000000">_Tp</span><span style="COLOR: #000000">&gt;</span><span style="COLOR: #000000">(&nbsp;t&nbsp;);<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_list.push_back(&nbsp;t&nbsp;);<br><img src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}</span></span><span style="COLOR: #000000">&nbsp;<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align=top><br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;template&nbsp;</span><span style="COLOR: #000000">&lt;</span><span style="COLOR: #000000">typename&nbsp;_Tp</span><span style="COLOR: #000000">&gt;</span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_Tp&nbsp;</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #0000ff">get</span><span style="COLOR: #000000">(&nbsp;size_t&nbsp;index&nbsp;)<br><img id=Codehighlighter1_632_823_Open_Image onclick="this.style.display='none'; Codehighlighter1_632_823_Open_Text.style.display='none'; Codehighlighter1_632_823_Closed_Image.style.display='inline'; Codehighlighter1_632_823_Closed_Text.style.display='inline';" src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif" align=top><img id=Codehighlighter1_632_823_Closed_Image style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_632_823_Closed_Text.style.display='none'; Codehighlighter1_632_823_Open_Image.style.display='inline'; Codehighlighter1_632_823_Open_Text.style.display='inline';" src="http://www.cppblog.com/Images/OutliningIndicators/ContractedSubBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span id=Codehighlighter1_632_823_Closed_Text style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff"><img src="http://www.cppblog.com/Images/dot.gif"></span><span id=Codehighlighter1_632_823_Open_Text><span style="COLOR: #000000">{<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;base_type&nbsp;</span><span style="COLOR: #000000">*</span><span style="COLOR: #0000ff">base</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;_list.at(&nbsp;index&nbsp;);<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;typedef&nbsp;var_wrapper</span><span style="COLOR: #000000">&lt;</span><span style="COLOR: #000000">_Tp</span><span style="COLOR: #000000">&gt;</span><span style="COLOR: #000000">&nbsp;var_type;<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;var_type&nbsp;</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">var&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;static_cast</span><span style="COLOR: #000000">&lt;</span><span style="COLOR: #000000">var_type</span><span style="COLOR: #000000">*&gt;</span><span style="COLOR: #000000">(&nbsp;</span><span style="COLOR: #0000ff">base</span><span style="COLOR: #000000">&nbsp;);<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">return</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">var;<br><img src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}</span></span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">private</span><span style="COLOR: #000000">:<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;TypeList&nbsp;_list;<br><img src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockEnd.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;}</span></span><span style="COLOR: #000000">;&nbsp;<br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span></div>
<p>&nbsp;
<p><font size=2>&nbsp;&nbsp;&nbsp; 说白了，我就是利用一个包装类将各种类型包装其中，然后利用基类指针实现统一管理。直白地说，我<br>对这个组件不满意。让人诟病的是，get接口是类型不安全的。例如：<br></p>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><span style="COLOR: #000000">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">&nbsp;a;&nbsp;<br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;CBaseObject&nbsp;obj;<br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;var_list&nbsp;my_var_list;<br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;my_var_list.add</span><span style="COLOR: #000000">&lt;</span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">&gt;</span><span style="COLOR: #000000">(&nbsp;a&nbsp;);<br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;my_var_list.add</span><span style="COLOR: #000000">&lt;</span><span style="COLOR: #000000">CBaseObject</span><span style="COLOR: #000000">&gt;</span><span style="COLOR: #000000">(&nbsp;obj&nbsp;);</span></div>
<p><br>&nbsp;&nbsp;&nbsp; 取出值的时候：<br></p>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><span style="COLOR: #000000">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">&nbsp;b&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;my_var_list.</span><span style="COLOR: #0000ff">get</span><span style="COLOR: #000000">&lt;</span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">&gt;</span><span style="COLOR: #000000">(&nbsp;</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">&nbsp;);<br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;CBaseObject&nbsp;cobj&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;my_var_list.</span><span style="COLOR: #0000ff">get</span><span style="COLOR: #000000">&lt;</span><span style="COLOR: #000000">CBaseObject</span><span style="COLOR: #000000">&gt;</span><span style="COLOR: #000000">(&nbsp;</span><span style="COLOR: #000000">1</span><span style="COLOR: #000000">&nbsp;);</span></div>
<p><br>&nbsp;&nbsp;&nbsp; 但是，因为get没有类型检查，即使你：<br></p>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><span style="COLOR: #000000">&nbsp;&nbsp;&nbsp;&nbsp;CBaseObject&nbsp;cobj&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;my_var_list.</span><span style="COLOR: #0000ff">get</span><span style="COLOR: #000000">&lt;</span><span style="COLOR: #000000">CBaseObject</span><span style="COLOR: #000000">&gt;</span><span style="COLOR: #000000">(&nbsp;</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">&nbsp;);</span></div>
<p><br>&nbsp;&nbsp;&nbsp; 也不会出错，编译器不会给予你警告。<br>&nbsp;&nbsp;&nbsp; 事情到此结束，这个类型不安全的组件只能依靠程序员自己的谨慎去生存。 </font>
<p><font size=2>&nbsp;&nbsp;&nbsp; 然后，又是一个不小心，我在boost里看到了any。boost::any库同boost::ref库一样，是一个tiny <br>library。几十行的代码一目了然。<br>&nbsp;&nbsp;&nbsp; boost::any有一个placeholder基类，有一个template &lt;typename ValueType&gt; holder派生类，然后有<br>一个提供给外部的any类。看了代码后有一种让我喷血的感觉。其实现原理和我自己的完全一致。<br>&nbsp;&nbsp;&nbsp; 比较而言，我觉得我的var_list撇脚到了极致。因为我封装了容器，而这显然是没必要的，并且限制<br>了其使用范围。而boost::any则是仅仅封装了类型。<br>&nbsp;&nbsp;&nbsp; 数据转换方面，boost::any提供了any_cast和unsafe_any_cast。unsafe_any_cast和我这里用的转换<br>差不多，也就是我说的类型不安全。而他的any_cast呢？则是用到了typeid，多了次类型检查而已。<br>&nbsp;&nbsp;&nbsp; 没办法，看来var_list需要被删掉，直接搬boost::any过来吧，同样地check out boost::any for more<br>information...<br>&nbsp;&nbsp;&nbsp; 现在看来，boost真的很强大。我感觉再怎么偏门的需求，都能在boost里找到个实现。痛定思痛，决定<br>把boost doc长期开着。 </font>
<p><font size=2></font></p>
<img src ="http://www.cppblog.com/kevinlynx/aggbug/64013.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/kevinlynx/" target="_blank">Kevin Lynx</a> 2008-10-15 11:23 <a href="http://www.cppblog.com/kevinlynx/archive/2008/10/15/64013.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>代码自动生成-宏递归思想</title><link>http://www.cppblog.com/kevinlynx/archive/2008/08/20/59451.html</link><dc:creator>Kevin Lynx</dc:creator><author>Kevin Lynx</author><pubDate>Wed, 20 Aug 2008 09:48:00 GMT</pubDate><guid>http://www.cppblog.com/kevinlynx/archive/2008/08/20/59451.html</guid><wfw:comment>http://www.cppblog.com/kevinlynx/comments/59451.html</wfw:comment><comments>http://www.cppblog.com/kevinlynx/archive/2008/08/20/59451.html#Feedback</comments><slash:comments>24</slash:comments><wfw:commentRss>http://www.cppblog.com/kevinlynx/comments/commentRss/59451.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/kevinlynx/services/trackbacks/59451.html</trackback:ping><description><![CDATA[<p><font size=2>Macro Recursion<br>author: Kevin Lynx </font>
<p><font size=2><strong>Preface </strong></font>
<p><font size=2>&nbsp;&nbsp;&nbsp; 本文可能是&lt;<a href="http://www.cppblog.com/kevinlynx/archive/2008/03/19/44828.html" target=_blank>代码自动生成-宏带来的奇技淫巧</a>&gt;的续写。我尽力阐述如何让宏递归（或者说重复）地有规律地产生一<br>些符号，而让我们少写很多重复代码，也许这些代码只有那么一点点的不同。将这项小技巧用于底层库的编写，会让代码<br>看起来干净不少，同时文件尺寸也会骤然下降。</font></p>
<font size=2>
<p><br><strong>Problem</strong></p>
<p><strong></strong><br>&nbsp;&nbsp;&nbsp; 如果你曾经写过<a href="http://www.cppblog.com/kevinlynx/archive/2008/03/17/44678.html" target=_blank>functor</a>，那么你肯定对某些代码进行粘贴复制然后修改。更让人郁闷的是，这些代码基本是一样的。<br>例如，一个典型的functor可能为：<br></p>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><span style="COLOR: #000000">&nbsp;&nbsp;&nbsp;&nbsp;template&nbsp;</span><span style="COLOR: #000000">&lt;</span><span style="COLOR: #000000">typename&nbsp;Prototype</span><span style="COLOR: #000000">&gt;</span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">class</span><span style="COLOR: #000000">&nbsp;functor;<br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;template&nbsp;</span><span style="COLOR: #000000">&lt;</span><span style="COLOR: #000000">typename&nbsp;R,&nbsp;typename&nbsp;P1</span><span style="COLOR: #000000">&gt;</span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">class</span><span style="COLOR: #000000">&nbsp;functor</span><span style="COLOR: #000000">&lt;</span><span style="COLOR: #000000">R(P1)</span><span style="COLOR: #000000">&gt;</span><span style="COLOR: #000000">;<br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;template&nbsp;</span><span style="COLOR: #000000">&lt;</span><span style="COLOR: #000000">typename&nbsp;R,&nbsp;typename&nbsp;P1,&nbsp;typename&nbsp;P2</span><span style="COLOR: #000000">&gt;</span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">class</span><span style="COLOR: #000000">&nbsp;functor</span><span style="COLOR: #000000">&lt;</span><span style="COLOR: #000000">R(P1,P2)</span><span style="COLOR: #000000">&gt;</span><span style="COLOR: #000000">;<br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span></div>
<p><br>&nbsp;&nbsp;&nbsp; //好，接下去你可能厌烦了，可能会复制一个带有两个参数的functor，然后修改为处理3个参数的。<br>&nbsp;&nbsp;&nbsp; 这只是一个很简单的问题。宏不是c++里的东西，本文自然也不是讨论各种花哨的模板技术的。如果我之前那篇关于<br>宏的文章只是让你去分析问题以及更深层次地认识宏，那么现在我将分享我的这部分思想给你。<br>&nbsp;&nbsp;&nbsp; 关于上面的问题，我们期待得到这样的解决方案：<br></p>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><span style="COLOR: #000000">&nbsp;&nbsp;&nbsp;&nbsp;template&nbsp;</span><span style="COLOR: #000000">&lt;</span><span style="COLOR: #000000">typename&nbsp;R,&nbsp;DEF_PARAM(&nbsp;</span><span style="COLOR: #000000">2</span><span style="COLOR: #000000">&nbsp;)</span><span style="COLOR: #000000">&gt;</span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">class</span><span style="COLOR: #000000">&nbsp;functor</span><span style="COLOR: #000000">&lt;</span><span style="COLOR: #000000">R(&nbsp;DEF_ARG(&nbsp;</span><span style="COLOR: #000000">2</span><span style="COLOR: #000000">&nbsp;)&nbsp;)</span><span style="COLOR: #000000">&gt;</span><span style="COLOR: #000000">;<br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span></div>
<p><br>&nbsp;&nbsp;&nbsp; 那么，它将自动生成：<br></font><font size=2></p>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><span style="COLOR: #000000">&nbsp;&nbsp;&nbsp;&nbsp;template&nbsp;</span><span style="COLOR: #000000">&lt;</span><span style="COLOR: #000000">typename&nbsp;R,&nbsp;typename&nbsp;P1,&nbsp;typename&nbsp;P2</span><span style="COLOR: #000000">&gt;</span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">class</span><span style="COLOR: #000000">&nbsp;functor</span><span style="COLOR: #000000">&lt;</span><span style="COLOR: #000000">R(P1,P2)</span><span style="COLOR: #000000">&gt;</span><span style="COLOR: #000000">;&nbsp;<br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span></div>
<p><br>&nbsp;&nbsp;&nbsp; 也就是说，DEF_PARAM(n)这个宏将根据n值自动生成一串符号，例如DEF_PARAM(2)就生成typename P1, typename P2。<br>同样，DEF_ARG(n)也会根据参数生成类似于P1,P2,...,Pn的符号串。 </font></p>
<p><font size=2><strong>思考</strong> </font>
<p><font size=2>&nbsp;&nbsp;&nbsp; 仔细思考下，我们可以看出DEF_PARAM和DEF_ARG这样的宏具有一种递归的特性（其实说成重复可能更合适）：每次展<br>开的内容基本一样，不断调用自身直到遇到终止条件。<br>&nbsp;&nbsp;&nbsp; 那么，我们的目标锁定于，用宏来实现递归。</font></p>
<p><font size=2><br><strong>Pre-Implement</strong> </font></p>
<p><font size=2>&nbsp;&nbsp;&nbsp; 在开始之前，我需要告诉你一些基本的东西：<br>&nbsp;&nbsp;&nbsp; 在阅读一个宏时，你最好按照预处理的处理方式去逐个展开。当我说到展开时，我的意思是把宏替换为宏体。预处理器<br>展开宏的过程大致为：如果宏参数也是个宏，那么先将宏参数全部展开，再展开该宏；这个时候会扫描展开后的宏，如果<br>遇到其他宏，则继续展开。例如有一下宏： </font>
<p><font size=2>&nbsp;</p>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;PI&nbsp;3.14</span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;MUL_PI(&nbsp;n&nbsp;)&nbsp;n&nbsp;*&nbsp;PI</span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;TWO&nbsp;2</span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span></div>
<p><br>&nbsp;&nbsp;&nbsp; 当我们写下MUL_PI( TWO )时，预处理发现MUL_PI的参数TWO 是个宏，那么先将TWO展开得到2，然后将2放进宏体展开<br>得到 2 * PI；预处理器对 2 * PI 进行扫描，发现还有宏PI，于是对PI做展开，得到 2 * 3.14。这个过程是递归的。<br>&nbsp;&nbsp;&nbsp; 但是也有例外，如果MUL_PI对宏参数进行了#或者##，那么该宏参数不会被展开。（参见以前那篇文章吧）<br>&nbsp;&nbsp;&nbsp; 任何时候，你可以通过以下宏去查看某个宏展开后的样子，可以方便你调试你的宏： </font>
<p><font size=2></p>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;TO_STRING(&nbsp;x&nbsp;)&nbsp;TO_STRING1(&nbsp;x&nbsp;)</span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;TO_STRING1(&nbsp;x&nbsp;)&nbsp;#x&nbsp;</span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span></div>
<p><br>&nbsp;&nbsp;&nbsp; （为什么要写个TO_STRING1，因为这是为了让x充分展开，避免上面提到的那个例外）&nbsp;&nbsp;&nbsp; </font>
<p><font size=2>&nbsp;&nbsp;&nbsp; 其他规则我会在文中需要的地方提出来。<br>实现 </font>
<p><font size=2>&nbsp;&nbsp;&nbsp; 就像大部分介绍递归函数时候给的例子，这里我也将阶乘作为例子。考虑如下典型的阶乘函数：<br></font>
<p><font size=2></p>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><span style="COLOR: #000000">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">&nbsp;fac(&nbsp;</span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">&nbsp;n&nbsp;)<br><img id=Codehighlighter1_25_95_Open_Image onclick="this.style.display='none'; Codehighlighter1_25_95_Open_Text.style.display='none'; Codehighlighter1_25_95_Closed_Image.style.display='inline'; Codehighlighter1_25_95_Closed_Text.style.display='inline';" src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif" align=top><img id=Codehighlighter1_25_95_Closed_Image style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_25_95_Closed_Text.style.display='none'; Codehighlighter1_25_95_Open_Image.style.display='inline'; Codehighlighter1_25_95_Open_Text.style.display='inline';" src="http://www.cppblog.com/Images/OutliningIndicators/ContractedBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;</span><span id=Codehighlighter1_25_95_Closed_Text style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff"><img src="http://www.cppblog.com/Images/dot.gif"></span><span id=Codehighlighter1_25_95_Open_Text><span style="COLOR: #000000">{<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">if</span><span style="COLOR: #000000">(&nbsp;n&nbsp;</span><span style="COLOR: #000000">==</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">1</span><span style="COLOR: #000000">&nbsp;)&nbsp;</span><span style="COLOR: #0000ff">return</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">1</span><span style="COLOR: #000000">;<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">return</span><span style="COLOR: #000000">&nbsp;n&nbsp;</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">&nbsp;fac(&nbsp;n&nbsp;</span><span style="COLOR: #000000">-</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">1</span><span style="COLOR: #000000">&nbsp;);<br><img src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockEnd.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;}</span></span><span style="COLOR: #000000">&nbsp;<br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span></div>
<p><br>&nbsp;&nbsp;&nbsp; 其核心部分在于 n * fac( n - 1 )，我们假设我们的宏也可以写成这样的的形式：<br></p>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><span style="COLOR: #000000">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;FAC(&nbsp;n&nbsp;)&nbsp;n&nbsp;*&nbsp;FAC(&nbsp;n&nbsp;-&nbsp;1&nbsp;)</span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span></div>
<p><br>&nbsp;&nbsp;&nbsp; 但是这样的宏是有问题的：<br>&nbsp;&nbsp;&nbsp; 当宏被展开时，如果遇到了自身，那么将被处理为一般符号，例如展开FAC( 3 )时，会遇到 FAC( 2 )，那么就把FAC<br>( 2 )中的FAC当成了一搬符号。<br>&nbsp;&nbsp;&nbsp; 这样的限制注定了我们无法让宏真正地调用自身来实现递归。于是，我们不得不写下以下丑陋的符号，从而去模拟递<br>归的每一次符号调用： </font>
<p><font size=2></p>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;FAC_1(&nbsp;n&nbsp;)&nbsp;1</span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;FAC_2(&nbsp;n&nbsp;)&nbsp;n&nbsp;*&nbsp;FAC_##(n-1)(&nbsp;n&nbsp;-&nbsp;1&nbsp;)</span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;FAC_3(&nbsp;n&nbsp;)&nbsp;n&nbsp;*&nbsp;FAC_##(n-1)(&nbsp;n&nbsp;-&nbsp;1&nbsp;)&nbsp;</span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span></div>
<p><br>&nbsp;&nbsp;&nbsp; 这系列宏有点别扭（如果你足够细心），因为我们明显知道FAC_2返回的将是2，而FAC_3返回的当时6。我们这里只是<br>模拟，同样，这使得我们可以把FAC_1作为递归的终止条件。<br>&nbsp;&nbsp;&nbsp; 我们的预想是，当调用FAC_3时，它把n-1的值2合并到FAC_中，从而调用FAC_2，以此类推。<br>&nbsp;&nbsp;&nbsp; 但是这依然有问题，编译器会提示&#8216;找不到符号FAC_&#8217;。导致这个问题的原因在于：宏展开只是单纯的字符替换，是我们<br>想太多了，预处理器并不会去计算( n - 1 )的值是多少，也就是我们无法得到FAC_2这个宏。 </font>
<p><font size=2>&nbsp;&nbsp;&nbsp; 所以，FAC_3( 3 ) 会被初次替换为 3 * FAC_(3-1)( 3 - 1 )。这个时候编译器就把FAC_当成了一个普通符号。我们可以<br>自己定义个FAC_来证明这一点： </font>
<p><font size=2>&nbsp;</p>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;FAC_(&nbsp;n&nbsp;)&nbsp;T&nbsp;</span></div>
<p></font>&nbsp;
<p><font size=2>&nbsp;&nbsp;&nbsp; 那么，FAC_3( 3 )就被替换为 3 * T(3-1)( 3 - 1 )。 </font>
<p><font size=2>&nbsp;&nbsp;&nbsp; 解决这个问题的办法关键在于，让预处理器自动计算出( n - 1 )。记住，我们解决问题的唯一办法是：字符替换。<br>所以，我们可以写下如下代码： </font>
<p><font size=2>&nbsp;</p>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;DEC_1&nbsp;0</span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;DEC_2&nbsp;1</span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;DEC_3&nbsp;2&nbsp;</span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;DEC(&nbsp;n&nbsp;)&nbsp;DEC_##n&nbsp;</span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span></div>
<p></font>&nbsp;
<p><font size=2>&nbsp;&nbsp;&nbsp; 通过，DEC( n )这个宏，我们可以获取到一个( n -1 )的数。例如，DEC( 3 )被替换为 DEC_3，继续替换为 2。 </font>
<p><font size=2>&nbsp;&nbsp;&nbsp; 于是，我们新的FAC系列宏变为： </font>
<p><font size=2>&nbsp;</p>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;FAC_1(&nbsp;n&nbsp;)&nbsp;1</span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;FAC_2(&nbsp;n&nbsp;)&nbsp;n&nbsp;*&nbsp;FAC_##DEC(&nbsp;n&nbsp;)(&nbsp;n&nbsp;-&nbsp;1&nbsp;)</span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;FAC_3(&nbsp;n&nbsp;)&nbsp;n&nbsp;*&nbsp;FAC_##DEC(&nbsp;n&nbsp;)(&nbsp;n&nbsp;-&nbsp;1&nbsp;)&nbsp;</span></div>
<p></font>&nbsp;
<p><font size=2>&nbsp;&nbsp;&nbsp; 不好意思，这样依然是不正确的！预处理器直接把FAC_和DEC( n )连接成符号了，而不是单个地先处理他们，最后再<br>合并他们。 </font>
<p><font size=2>&nbsp;&nbsp;&nbsp; OK，先解决这个问题：先处理FAC_和DEC( n )，再合并他们，而不是先合并他们。要解决这个问题，可以通过第三个宏<br>来实现： </font>
<p><font size=2>&nbsp;</p>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;CHR(&nbsp;x,&nbsp;y&nbsp;)&nbsp;x##y&nbsp;</span></div>
<p></font>&nbsp;
<p><font size=2>&nbsp;&nbsp;&nbsp; 作为连接两个符号为一个符号的宏，这个宏显然是不正确的，因为宏展开还有个规则：如果宏体对宏参数使用了#或##，<br>那么宏参数不会被展开，也就是说：如果CHR( FAC_, DEC( 3 ) 那么得到的只会是 FAC_DEC( 3 )。通常情况下我们是<br>再写个宏： </font>
<p><font size=2>&nbsp;</p>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;CHR(&nbsp;x,&nbsp;y&nbsp;)&nbsp;CHR1(&nbsp;x,&nbsp;y&nbsp;)</span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;CHR1(&nbsp;x,&nbsp;y&nbsp;)&nbsp;x##y&nbsp;</span></div>
<p></font>&nbsp;
<p><font size=2>&nbsp;&nbsp;&nbsp; 从而可以保证在正式连接x和y前，x和y都被完全展开。 </font>
<p><font size=2>&nbsp;&nbsp;&nbsp; 这个时候，我们的FAC系列宏变为： </font>
<p><font size=2>&nbsp;</p>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;FAC_1(&nbsp;n&nbsp;)&nbsp;1</span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;FAC_2(&nbsp;n&nbsp;)&nbsp;n&nbsp;*&nbsp;CHR(&nbsp;FAC_,&nbsp;DEC(&nbsp;n&nbsp;)&nbsp;)(&nbsp;n&nbsp;-&nbsp;1&nbsp;)</span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;FAC_3(&nbsp;n&nbsp;)&nbsp;n&nbsp;*&nbsp;CHR(&nbsp;FAC_,&nbsp;DEC(&nbsp;n&nbsp;)&nbsp;)(&nbsp;n&nbsp;-&nbsp;1&nbsp;)&nbsp;</span></div>
<p></font>&nbsp;
<p><font size=2>&nbsp;&nbsp;&nbsp; 结果呢？结果还是有问题。= =<br>&nbsp;&nbsp;&nbsp; 我们假设CHR( FAC_, DEC( n ) )已经真的按我们预想展开为 FAC_2了，那么FAC_3( 3 )会被展开为什么呢？<br>被展开为 3 * FAC_2( 3 - 1 )。这是错误的，传给 FAC_2 的参数是 3 - 1就意味着错误。我们又臆想预处理器会<br>帮我们计算 3 - 1的结果了。我们必须保证传给 FAC_2的参数是个数字2。解决这个问题的办法就是通过DEC(n)宏。 </font>
<p><font size=2>&nbsp;&nbsp; 于是，FAC系列宏变为： </font>
<p><font size=2>&nbsp;</p>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;FAC_1(&nbsp;n&nbsp;)&nbsp;1</span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;FAC_2(&nbsp;n&nbsp;)&nbsp;n&nbsp;*&nbsp;CHR(&nbsp;FAC_,&nbsp;DEC(&nbsp;n&nbsp;)&nbsp;)(&nbsp;DEC(&nbsp;n&nbsp;)&nbsp;)</span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;FAC_3(&nbsp;n&nbsp;)&nbsp;n&nbsp;*&nbsp;CHR(&nbsp;FAC_,&nbsp;DEC(&nbsp;n&nbsp;)&nbsp;)(&nbsp;DEC(&nbsp;n&nbsp;)&nbsp;)&nbsp;</span></div>
<p></font>&nbsp;
<p><font size=2>&nbsp;&nbsp;&nbsp; 这个时候，FAC_3( 3 )将会被替换为：3*2*1。这就是我们要的结果。 </font>
<p><font size=2><strong>In practice</strong> </font>
<p><font size=2>&nbsp;&nbsp;&nbsp; 以上只是向你展示一个过程，用宏去计算阶乘，就像用模板去计算阶乘（模板元编程）一样，只是一个用于展示的东西，<br>没有什么实际价值。接下来我们开始有实际的工作，完成之前的预想： </font>
<p><font size=2>&nbsp;</p>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><span style="COLOR: #000000">template&nbsp;</span><span style="COLOR: #000000">&lt;</span><span style="COLOR: #000000">typename&nbsp;R,&nbsp;typename&nbsp;P1,&nbsp;typename&nbsp;P2,&nbsp;typename&nbsp;P3</span><span style="COLOR: #000000">&gt;</span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">class</span><span style="COLOR: #000000">&nbsp;functor</span><span style="COLOR: #000000">&lt;</span><span style="COLOR: #000000">R&nbsp;(P1,&nbsp;P2,&nbsp;P3)</span><span style="COLOR: #000000">&gt;</span><span style="COLOR: #000000">&nbsp;</span></div>
<p></font>&nbsp;
<p><font size=2>&nbsp;&nbsp;&nbsp; 直接: </font>
<p><font size=2>&nbsp;</p>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><span style="COLOR: #000000">template&nbsp;</span><span style="COLOR: #000000">&lt;</span><span style="COLOR: #000000">typename&nbsp;R,&nbsp;PARAM(&nbsp;</span><span style="COLOR: #000000">3</span><span style="COLOR: #000000">&nbsp;)</span><span style="COLOR: #000000">&gt;</span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">class</span><span style="COLOR: #000000">&nbsp;functor</span><span style="COLOR: #000000">&lt;</span><span style="COLOR: #000000">R&nbsp;(ARG(&nbsp;</span><span style="COLOR: #000000">3</span><span style="COLOR: #000000">&nbsp;))</span><span style="COLOR: #000000">&gt;</span><span style="COLOR: #000000">&nbsp;</span></div>
<p></font>&nbsp;
<p><font size=2>&nbsp;&nbsp;&nbsp; 先考虑PARAM宏，该宏的任务就是生成类似于：typename P1, typename P2, typename P3这样的符号。我们假象它每一次<br>递归都生成 typename Pn, 的字符串，那么当他递归完时，可能就生成typename P1, typename P2, typename P3, 结果<br>多了个逗号，也许最后一次结果不该有逗号。 </font>
<p><font size=2>&nbsp;&nbsp;&nbsp; ARG宏和PARAM宏本质上相同，只是重复的符号不是typename Pn，而是Pn。 </font>
<p><font size=2>&nbsp;&nbsp;&nbsp; 最直接想到的是： </font>
<p><font size=2>&nbsp;</p>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;PARAM_1(&nbsp;n&nbsp;)&nbsp;typename&nbsp;P##n</span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;PARAM_2(&nbsp;n&nbsp;)&nbsp;CHR(&nbsp;PARAM_,&nbsp;DEC(&nbsp;n&nbsp;)&nbsp;)(&nbsp;DEC(&nbsp;n&nbsp;)&nbsp;)##,typename&nbsp;P##n</span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;PARAM_3(&nbsp;n&nbsp;)&nbsp;CHR(&nbsp;PARAM_,&nbsp;DEC(&nbsp;n&nbsp;)&nbsp;)(&nbsp;DEC(&nbsp;n&nbsp;)&nbsp;)##,typename&nbsp;P##n&nbsp;</span></div>
<p></font>&nbsp;
<p><font size=2>&nbsp;&nbsp;&nbsp; 结果我们得到了个错误的展开结果：<br>typename PDEC( 2 ),typename PDEC( 3 ),typename P3 </font>
<p><font size=2>&nbsp;&nbsp;&nbsp; 这个问题出在：PARAM_3( 3 )当替换为 PARAM_2( DEC( n ) )时，因为PARAM_2(n)宏对于宏参数n使用了##，也就是那个<br>typename P##n，所以这里不会把 DEC( n )展开，而是直接接到P后面。所以就成了typename PDEC( 3 )。 </font>
<p><font size=2>&nbsp;&nbsp;&nbsp; 为了消除这个问题，我们改进PARAM为： </font>
<p><font size=2>&nbsp;</p>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;TYPE(&nbsp;n&nbsp;)&nbsp;,typename&nbsp;P##n</span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;PARAM_1(&nbsp;n&nbsp;)&nbsp;CHR(&nbsp;typename&nbsp;P,&nbsp;n&nbsp;)</span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;PARAM_2(&nbsp;n&nbsp;)&nbsp;CHR(&nbsp;CHR(&nbsp;PARAM_,&nbsp;DEC(&nbsp;n&nbsp;)&nbsp;)(&nbsp;DEC(&nbsp;n&nbsp;)&nbsp;),&nbsp;TYPE(&nbsp;n&nbsp;)&nbsp;)</span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;PARAM_3(&nbsp;n&nbsp;)&nbsp;CHR(&nbsp;CHR(&nbsp;PARAM_,&nbsp;DEC(&nbsp;n&nbsp;)&nbsp;)(&nbsp;DEC(&nbsp;n&nbsp;)&nbsp;),&nbsp;TYPE(&nbsp;n&nbsp;)&nbsp;)&nbsp;</span></div>
<p></font>&nbsp;
<p><font size=2>&nbsp;&nbsp;&nbsp; 之所以加入TYPE(n)，是因为 ,typename P##n 这个宏本身存在逗号，将其直接用于宏体会出现问题。 </font>
<p><font size=2>&nbsp;&nbsp;&nbsp; 于是，我们得到了正确的结果。 </font>
<p><font size=2>&nbsp;&nbsp;&nbsp; 其实，PARAM系列宏宏体基本是一样的，除了终止条件那个宏，为什么我们要写这么多呢？理由在于宏体不能自己调用<br>自己，所以才有了PARAM_3, PARAM_2。 </font>
<p><font size=2>&nbsp;&nbsp;&nbsp; 我们可以将上面的一系列宏抽象化，使其具有可复用性： </font>
<p><font size=2>&nbsp;</p>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;PARAM(&nbsp;n&nbsp;)&nbsp;,typename&nbsp;P##n</span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;PARAM_END&nbsp;typename&nbsp;P&nbsp;</span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;ARG(&nbsp;n&nbsp;)&nbsp;,P##n</span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;ARG_END&nbsp;P&nbsp;</span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;PARAM_1(&nbsp;n&nbsp;)&nbsp;CHR(&nbsp;typename&nbsp;P,&nbsp;n&nbsp;)</span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;PARAM_2(&nbsp;n&nbsp;)&nbsp;CHR(&nbsp;CHR(&nbsp;PARAM_,&nbsp;DEC(&nbsp;n&nbsp;)&nbsp;)(&nbsp;DEC(&nbsp;n&nbsp;)&nbsp;),&nbsp;TYPE(&nbsp;n&nbsp;)&nbsp;)</span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;PARAM_3(&nbsp;n&nbsp;)&nbsp;CHR(&nbsp;CHR(&nbsp;PARAM_,&nbsp;DEC(&nbsp;n&nbsp;)&nbsp;)(&nbsp;DEC(&nbsp;n&nbsp;)&nbsp;),&nbsp;TYPE(&nbsp;n&nbsp;)&nbsp;)&nbsp;</span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;REPEAT_1(&nbsp;n,&nbsp;f,&nbsp;e&nbsp;)&nbsp;CHR(&nbsp;e,&nbsp;n&nbsp;)</span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;REPEAT_2(&nbsp;n,&nbsp;f,&nbsp;e&nbsp;)&nbsp;CHR(&nbsp;CHR(&nbsp;REPEAT_,&nbsp;DEC(&nbsp;n&nbsp;)&nbsp;)(&nbsp;DEC(&nbsp;n&nbsp;),&nbsp;f,&nbsp;e&nbsp;),&nbsp;f(&nbsp;n&nbsp;)&nbsp;)</span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;REPEAT_3(&nbsp;n,&nbsp;f,&nbsp;e&nbsp;)&nbsp;CHR(&nbsp;CHR(&nbsp;REPEAT_,&nbsp;DEC(&nbsp;n&nbsp;)&nbsp;)(&nbsp;DEC(&nbsp;n&nbsp;),&nbsp;f,&nbsp;e&nbsp;),&nbsp;f(&nbsp;n&nbsp;)&nbsp;)&nbsp;</span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;DEF_PARAM(&nbsp;n&nbsp;)&nbsp;REPEAT_##n(&nbsp;n,&nbsp;PARAM,&nbsp;PARAM_END&nbsp;)</span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;DEF_ARG(&nbsp;n&nbsp;)&nbsp;REPEAT_##n(&nbsp;n,&nbsp;ARG,&nbsp;ARG_END&nbsp;)&nbsp;</span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span></div>
<p></font>&nbsp;
<p><font size=2>&nbsp;&nbsp;&nbsp; 我们创建了可重用的REPEAT系列宏，用于创建诸如typename P1, typename P2, typename P3或者P1,P2,P3之类的符号，<br>通过更上层的DEF_PARAM(n)和DEF_ARG(n)，就可以直接创建出我们上面所需要的符号串，例如： </font>
<p><font size=2>&nbsp;&nbsp;&nbsp; DEF_PARAM( 3 ) 就得到 typename P1, typename P2, typename P3<br>&nbsp;&nbsp;&nbsp; DEF_ARG( 3 ) 就得到 P1, P2, P3 </font>
<p><font size=2><strong>More in practice</strong> </font>
<p><font size=2>&nbsp;&nbsp;&nbsp; 下载中提供了我使用这个宏递归技术写的lua_binder(如果你看过&lt;<a href="http://www.cppblog.com/kevinlynx/archive/2008/08/13/58684.html" target=_blank>实现自己的LUA绑定器-一个模板编程挑战</a> &gt;)，你<br>可以与上一个版本做一下比较，代码少了很多。<br>&nbsp;&nbsp;&nbsp; 同样，我希望你也能获取这种宏递归的思想。&nbsp;&nbsp;&nbsp; </font></p>
<p><font size=2><strong>相关下载</strong></font></p>
<p><font size=2>&nbsp;&nbsp; <a href="http://www.cppblog.com/Files/kevinlynx/mr_luabinder.zip" target=_blank>使用宏递归的lua_binder</a></font></p>
<img src ="http://www.cppblog.com/kevinlynx/aggbug/59451.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/kevinlynx/" target="_blank">Kevin Lynx</a> 2008-08-20 17:48 <a href="http://www.cppblog.com/kevinlynx/archive/2008/08/20/59451.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>