<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>守望的个人博客</title>
  
  <subtitle>编程珠玑</subtitle>
  <link href="/atom.xml" rel="self"/>
  
  <link href="https://www.yanbinghu.com/"/>
  <updated>2021-06-06T06:53:46.658Z</updated>
  <id>https://www.yanbinghu.com/</id>
  
  <author>
    <name>守望</name>
    
  </author>
  
  <generator uri="http://hexo.io/">Hexo</generator>
  
  <entry>
    <title>多线程进程fork出来的进程是单线程还是多线程？</title>
    <link href="https://www.yanbinghu.com/2021/06/06/51898.html"/>
    <id>https://www.yanbinghu.com/2021/06/06/51898.html</id>
    <published>2021-06-06T06:30:00.000Z</published>
    <updated>2021-06-06T06:53:46.658Z</updated>
    
    <content type="html"><![CDATA[<p>一个多线程进程fork出来的进程是多线程还是单线程的？先说结论：是单线程的。</p><a id="more"></a><h2 id="实践"><a href="#实践" class="headerlink" title="实践"></a>实践</h2><p>口说无凭，我们先写段代码实践验证一下。<br><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 来源：公众号【编程珠玑】</span></span><br><span class="line"><span class="comment">// 作者：守望先生</span></span><br><span class="line"><span class="comment">// multiThread.cc</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;unistd.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;atomic&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;chrono&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;thread&gt;</span></span></span><br><span class="line"><span class="built_in">std</span>::atomic&lt;<span class="keyword">bool</span>&gt; start&#123;<span class="literal">false</span>&#125;;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">threadfunc</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  <span class="keyword">while</span> (!start) &#123;</span><br><span class="line">    <span class="built_in">std</span>::this_thread::sleep_for(<span class="built_in">std</span>::chrono::seconds(<span class="number">1</span>));</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">while</span> (start) &#123;</span><br><span class="line">    <span class="built_in">std</span>::this_thread::sleep_for(<span class="built_in">std</span>::chrono::seconds(<span class="number">1</span>));</span><br><span class="line">    <span class="built_in">std</span>::<span class="built_in">cout</span> &lt;&lt; <span class="string">"thread func，pid:"</span> &lt;&lt; getpid() &lt;&lt; <span class="built_in">std</span>::<span class="built_in">endl</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  <span class="function"><span class="built_in">std</span>::thread <span class="title">t1</span><span class="params">(threadfunc)</span></span>;</span><br><span class="line">  <span class="comment">// daemon(0, 1); // 后台执行</span></span><br><span class="line">  start.store(<span class="literal">true</span>);</span><br><span class="line">  t1.join();  <span class="comment">// 等待threadfunc运行结束</span></span><br><span class="line">  <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>编译运行：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">$ g++ -o multiThread multiThread.cc -lphtread</span><br><span class="line">$ ./multiThread</span><br><span class="line">thread func，pid:9901</span><br><span class="line">thread func，pid:9901</span><br><span class="line">thread func，pid:9901</span><br></pre></td></tr></table></figure></p><p>结果正常，线程不断循环打印信息。那如果启动线程后，再fork呢？即将代码中daemon的相关行的注释去掉，再编译运行。</p><p>在《<a href="https://www.yanbinghu.com/2019/12/06/39731.html">如何让程序真正地后台运行？</a>》中我们知道，daemon实际上做了进程的fork。</p><p>运行这个例子，我们会发现，程序立马退出了，没有打印我们预想的内容。</p><h2 id="为什么"><a href="#为什么" class="headerlink" title="为什么"></a>为什么</h2><p>为什么会这样呢？实际上，我们在《<a href="https://www.yanbinghu.com/2019/08/11/28423.html">如何使用fork创建进程</a>》中就提到过，fork的时候会拷贝父进程的数据内容，即写时复制，但是，像启动运行的线程，是不会被“复制”过去的。也就是说，从父进程fork出来的子进程，将会是单线程的。这也就给了我们一些启示</p><ul><li>如果在API中需要启动工作线程，则工作线程需要在daemon化之后再启动</li></ul><p>怎么理解呢？比如说，你设计了某一个功能，你的功能是需要启动一个线程来进程工作，那么你在使用的时候，就必须要特别注意这种fork进程的场景，即需要在fork之后启动线程，才能保证线程能够正常启动并工作。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;一个多线程进程fork出来的进程是多线程还是单线程的？先说结论：是单线程的。&lt;/p&gt;
    
    </summary>
    
      <category term="C" scheme="https://www.yanbinghu.com/categories/C/"/>
    
    
      <category term="C" scheme="https://www.yanbinghu.com/tags/C/"/>
    
  </entry>
  
  <entry>
    <title>shell逐行读取文件的坑</title>
    <link href="https://www.yanbinghu.com/2021/06/05/57932.html"/>
    <id>https://www.yanbinghu.com/2021/06/05/57932.html</id>
    <published>2021-06-05T10:00:00.000Z</published>
    <updated>2021-06-05T10:12:12.047Z</updated>
    
    <content type="html"><![CDATA[<h2 id="shell逐行读取文本，我人傻了"><a href="#shell逐行读取文本，我人傻了" class="headerlink" title="shell逐行读取文本，我人傻了"></a>shell逐行读取文本，我人傻了</h2><p>假设要要计算文本test.data的第二列的数字之和：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">1 12 </span><br><span class="line">2 23 </span><br><span class="line">3 34 </span><br><span class="line">4 56</span><br></pre></td></tr></table></figure></p><p>当然你可能会这样处理：<br><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">awk <span class="string">'&#123;s+=$2&#125; END &#123;print s&#125;'</span> test.data</span><br></pre></td></tr></table></figure></p><p>很快就得到了结果。不过，本文要说的点与awk无关。我们通过另外一种方式来计算，即逐行分析处理的方式。<br><a id="more"></a></p><h2 id="尝试一"><a href="#尝试一" class="headerlink" title="尝试一"></a>尝试一</h2><p>我们尝试第一种方式，shell实现如下：<br><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/usr/bin/env bash</span></span><br><span class="line">sum=0</span><br><span class="line">cat test.data | <span class="keyword">while</span> <span class="built_in">read</span> line</span><br><span class="line"><span class="keyword">do</span></span><br><span class="line">    temp_num=$(<span class="built_in">echo</span> <span class="variable">$line</span> | cut -d <span class="string">' '</span> -f 2)</span><br><span class="line">    sum=$(( <span class="variable">$sum</span> + <span class="variable">$temp_num</span> ))</span><br><span class="line"><span class="keyword">done</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">"we get sum:<span class="variable">$sum</span>"</span></span><br></pre></td></tr></table></figure></p><p>输出结果：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">we get sum:0</span><br></pre></td></tr></table></figure></p><p>这是为什么！为什么得到的结果会是0呢？</p><p>这事坏就坏在脚本中的|，众所周知，这是一个管道命令，而这也就意味着，while循环的执行结果都是在一个subshell中，一旦这个subsell退出了，它里面的结果也就没有了。</p><h2 id="尝试二"><a href="#尝试二" class="headerlink" title="尝试二"></a>尝试二</h2><p>既然管道命令不建议用，那么我们使用下面的方式看看：<br><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/usr/bin/env bash</span></span><br><span class="line">sum=0</span><br><span class="line"><span class="keyword">for</span> line <span class="keyword">in</span> $(cat test.data)</span><br><span class="line"><span class="keyword">do</span></span><br><span class="line">    <span class="built_in">echo</span> <span class="string">"get line :<span class="variable">$line</span>"</span></span><br><span class="line">    temp_num=$(<span class="built_in">echo</span> <span class="variable">$line</span> | cut -d <span class="string">' '</span> -f 2)</span><br><span class="line">    sum=$(( <span class="variable">$sum</span> + <span class="variable">$temp_num</span> ))</span><br><span class="line"><span class="keyword">done</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">"we get sum:<span class="variable">$sum</span>"</span></span><br></pre></td></tr></table></figure></p><p>输出结果：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">get line :1</span><br><span class="line">get line :12</span><br><span class="line">get line :2</span><br><span class="line">get line :23</span><br><span class="line">get line :3</span><br><span class="line">get line :34</span><br><span class="line">get line :4</span><br><span class="line">get line :56</span><br><span class="line">we get sum:135</span><br></pre></td></tr></table></figure></p><p>从结果中看出，如果文本中存在空格或者tab等，则看似每次读取一行，实际上是遇到空格,tab或换行就停止读取了，并没有达到我们的目的。<br>我们预期的应该是遇到换行才停止读取，为了达到这个目的，我们可以设置这个标记，即通过设置IFS来达到目的。在上面的shell开头加上：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">IFS=$&apos;\n&apos;</span><br></pre></td></tr></table></figure></p><p>但是修改为这样之后，在自己的系统上并没有得到我想要的效果，有知道的读者可以告知一下。</p><h2 id="尝试三"><a href="#尝试三" class="headerlink" title="尝试三"></a>尝试三</h2><p>让我们再换一种方式：<br><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/usr/bin/env bash</span></span><br><span class="line">sum=0</span><br><span class="line"><span class="keyword">while</span> <span class="built_in">read</span> line</span><br><span class="line"><span class="keyword">do</span></span><br><span class="line">    <span class="built_in">echo</span> <span class="string">"line <span class="variable">$line</span>"</span></span><br><span class="line">    temp_num=$(<span class="built_in">echo</span> <span class="variable">$line</span> | cut -d <span class="string">' '</span> -f 2)</span><br><span class="line">    sum=$(( <span class="variable">$sum</span> + <span class="variable">$temp_num</span> ))</span><br><span class="line"><span class="keyword">done</span> &lt; <span class="string">"test.data"</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">"we get sum:<span class="variable">$sum</span>"</span></span><br></pre></td></tr></table></figure></p><p>这种方式我们是能得到正确结果的。<br>当然，如果你要读取指定列，你还可以像下面这样做：<br><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/usr/bin/env bash</span></span><br><span class="line">sum=0</span><br><span class="line"><span class="keyword">while</span> <span class="built_in">read</span> col1 col2</span><br><span class="line"><span class="keyword">do</span></span><br><span class="line">    sum=$(( <span class="variable">$sum</span> + <span class="variable">$col2</span> ))</span><br><span class="line"><span class="keyword">done</span> &lt; <span class="string">"test.data"</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">"we get sum:<span class="variable">$sum</span>"</span></span><br></pre></td></tr></table></figure></p><p>其中col1，col2就分别代表了第一列，第二列，使用的时候，可以直接使用对应列的内容。</p><p>但是，如果我们要读取的内容包括了转义字符会怎么办？例如：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">\n 12</span><br><span class="line">\n 23</span><br><span class="line">\n 34</span><br><span class="line">\n 56</span><br></pre></td></tr></table></figure></p><p>执行结果：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">line </span><br><span class="line"> 12</span><br><span class="line">line </span><br><span class="line"> 23</span><br><span class="line">line </span><br><span class="line"> 34</span><br><span class="line">line </span><br><span class="line"> 56</span><br><span class="line">we get sum:125</span><br></pre></td></tr></table></figure></p><p>从结果可以看到，虽然内容能否读取到，但是内容被打印出来的时候，已经变了，\被当成转义字符处理了，如果不想让它转义处理怎么办？只需要加上-r参数即可：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">while read -r line</span><br></pre></td></tr></table></figure></p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>在逐行处理文本过程中，主要关注以下几种情况：</p><ul><li>行中有空格，tab</li><li>行中有转义字符</li></ul>]]></content>
    
    <summary type="html">
    
      &lt;h2 id=&quot;shell逐行读取文本，我人傻了&quot;&gt;&lt;a href=&quot;#shell逐行读取文本，我人傻了&quot; class=&quot;headerlink&quot; title=&quot;shell逐行读取文本，我人傻了&quot;&gt;&lt;/a&gt;shell逐行读取文本，我人傻了&lt;/h2&gt;&lt;p&gt;假设要要计算文本test.data的第二列的数字之和：&lt;br&gt;&lt;figure class=&quot;highlight plain&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1 12 &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2 23 &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3 34 &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4 56&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;&lt;/p&gt;
&lt;p&gt;当然你可能会这样处理：&lt;br&gt;&lt;figure class=&quot;highlight sh&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;awk &lt;span class=&quot;string&quot;&gt;&#39;&amp;#123;s+=$2&amp;#125; END &amp;#123;print s&amp;#125;&#39;&lt;/span&gt; test.data&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;&lt;/p&gt;
&lt;p&gt;很快就得到了结果。不过，本文要说的点与awk无关。我们通过另外一种方式来计算，即逐行分析处理的方式。&lt;br&gt;
    
    </summary>
    
      <category term="linux" scheme="https://www.yanbinghu.com/categories/linux/"/>
    
    
      <category term="linux" scheme="https://www.yanbinghu.com/tags/linux/"/>
    
  </entry>
  
  <entry>
    <title>程序是如何找到动态库的？</title>
    <link href="https://www.yanbinghu.com/2021/03/13/33445.html"/>
    <id>https://www.yanbinghu.com/2021/03/13/33445.html</id>
    <published>2021-03-13T14:00:00.000Z</published>
    <updated>2021-03-13T13:54:38.802Z</updated>
    
    <content type="html"><![CDATA[<p>我们都知道，二进制程序文件和动态库通常并不是完全绑定在一起发布的，那么可执行程序运行时，是如何找到动态库的呢？<br><a id="more"></a><br>我们随便开发一个C/C++程序，都很大程度不可避免的需要用到动态库：<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 来源：公众号【编程珠玑】</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"hello，编程珠玑\n"</span>);</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>编译并查看使用到的动态库：<br><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> gcc -o main main.c</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> ldd main</span></span><br><span class="line">linux-vdso.so.1 (0x00007ffdf8fdf000)</span><br><span class="line">libc.so.6 =&gt; /lib/x86_64-linux-gnu/libc.so.6 (0x00007f1f8535e000)</span><br><span class="line">/lib64/ld-linux-x86-64.so.2 (0x00007f1f85951000)</span><br></pre></td></tr></table></figure></p><p>从ldd命令的结果我们可以看到main程序依赖了哪些动态库，并且在哪个路径。那么你有没有想过，动态库的路径是怎么找到的，查找顺序又是怎样的呢？</p><h2 id="准备动态库"><a href="#准备动态库" class="headerlink" title="准备动态库"></a>准备动态库</h2><p>在此之前如果你还没有对动态库有一个基本的了解的话，建议你阅读《<a href="https://www.yanbinghu.com/2019/06/27/47343.html">浅谈静态库和动态库</a>》或其他相关资料。为了说明后面的问题，这里我们先创建一个简单的动态库，你也可以参考《<a href="https://www.yanbinghu.com/2019/07/18/38654.html">手把手教你制作动态库</a>》：<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// test.c</span></span><br><span class="line"><span class="comment">//来源：公众号【编程珠玑】</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"test.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"test1.h"</span></span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">test</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"I am test；hello，编程珠玑\n"</span>);</span><br><span class="line">    test1();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// test.h</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">test</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">//test1.c</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"test1.h"</span></span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">test1</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"test1,needed by test\n"</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// test1.h</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">test1</span><span class="params">()</span></span>;</span><br></pre></td></tr></table></figure></p><p>分别制作动态库libtest.so和libtest1.so，这在后面的示例中会用到：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ gcc test1.c -fPIC -shared -o libtest1.so</span><br><span class="line">$ gcc test.c -fPIC -shared -o libtest.so -L. -ltest1</span><br></pre></td></tr></table></figure></p><p>这样你在当前目录下就会看到有一个libtest.so和libtest1.so文件生成了，其中litest.so依赖libtest.so</p><p>注意，由于libtest.so依赖libtest1.so，这里用-L指定了要链接的test1的路径，因此我们看到：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">$ ldd libtest.so</span><br><span class="line">linux-vdso.so.1 (0x00007ffd1bbca000)</span><br><span class="line">libtest1.so =&gt; not found</span><br><span class="line">libc.so.6 =&gt; /lib/x86_64-linux-gnu/libc.so.6 (0x00007f9f1d0ae000)</span><br><span class="line">/lib64/ld-linux-x86-64.so.2 (0x00007f9f1d6a1000)</span><br></pre></td></tr></table></figure></p><p>从这里可以看出libtest是依赖libtest1库的，但是特别注意到，libtest1.so指向的是not found，这会有什么影响吗？我们后面就会看到。</p><h2 id="链接时查找路径"><a href="#链接时查找路径" class="headerlink" title="链接时查找路径"></a>链接时查找路径</h2><p>我们都知道，在编译成可执行文件前，链接器链接动态库也是需要查找动态库路径的，否则怎么链接上指定的动态库呢？那么这个顺序又是怎样的呢？</p><p>首先会查找的会是编译时链接的路径。修改前面的main.c，让它调用libtest.so中的test函数：<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 来源：公众号【编程珠玑】</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"test.h"</span></span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    test(); <span class="comment">// 调用libtest.so中的test函数</span></span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>编译链接：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ gcc -o main main.c -I ./ -L./ -ltest -ltest1</span><br></pre></td></tr></table></figure></p><p>完美编译过。除此之外，如果我们把libtest.so和libtest1.so都移到/usr/lib下面，我们发现，即便不用-L也能编译过了:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ gcc -o main main.c -I ./  -ltest -ltest1</span><br></pre></td></tr></table></figure></p><p>这里需要说明的是，我们通过-L./来指定搜索库的路径，由于libtest.so依赖libtest1.so，因此在编译链接时，也需要链接上test1。</p><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>从上面的内容可以看到，在链接时，我们通过-L参数搜索要链接的库路径，但是这个路径信息不会写到ELF文件中，因此你会通过ldd命令看到not found，而通过-rpath可以指定链接时的搜索路径，这个信息会写入到ELF文件中，最终看到的结果是，由于libtest.so依赖libtest1.so，所以其他程序依赖libtest.so时，会自动从写入ELF的rpath中搜索它依赖的其他库，因此只需要链接libtest即可，例如：</p><p>在制作库libtest1.so时，加上-rpath-link选项：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ gcc test.c -fPIC -shared -o libtest.so -L. -ltest1 -Wl,-rpath-link $(pwd)</span><br></pre></td></tr></table></figure></p><p>在编译main的时候，就不需要特意指定链接libtest1.so<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ gcc -o main main.c -L ./ -ltest</span><br></pre></td></tr></table></figure></p><p>只需要链接libtest.so，其依赖的libtest1.so也链接进来了。<br>当然了，如果-L指定的路径没有呢,它还会去查找其他地方，否则依赖的系统库怎么找到呢？总结大致顺序如下：</p><ul><li>-L指定链接路径</li><li>对于依赖库中依赖的搜索顺序通过-rpath-link或-rpath选项查找（后面会提到）</li><li>gcc默认链接路径（gcc —print-search-dir | grep libraries 查看）</li><li>链接器配置的查找路径（ld -verbose | grep SEARCH_DIR查看）</li></ul><p>针对具体的系统或链接器，可能有些差异，但是大致如此。</p><h2 id="运行时查找路径"><a href="#运行时查找路径" class="headerlink" title="运行时查找路径"></a>运行时查找路径</h2><p>虽然前面编译成功了，但是我们运行看看，发现运行失败了。<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ ./main</span><br><span class="line">./main: error <span class="keyword">while</span> loading shared libraries: libtest.so: cannot open shared object file: No such file <span class="keyword">or</span> directory</span><br></pre></td></tr></table></figure></p><p>其实我们用ldd命令看一下也能看到：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">linux-vdso.so.1 (0x00007ffcd566e000)</span><br><span class="line">libtest.so =&gt; not found</span><br><span class="line">libc.so.6 =&gt; /lib/x86_64-linux-gnu/libc.so.6 (0x00007f356d1f6000)</span><br><span class="line">/lib64/ld-linux-x86-64.so.2 (0x00007f356d7e9000)</span><br></pre></td></tr></table></figure></p><h2 id="LD-PRELOAD环境变量"><a href="#LD-PRELOAD环境变量" class="headerlink" title="LD_PRELOAD环境变量"></a>LD_PRELOAD环境变量</h2><p>这个环境变量在介绍《<a href="https://www.yanbinghu.com/2020/12/20/39641.html">性能优化-使用高效内存分配器</a>》中的时候，也有提到，用来做测试非常方便，同样的，这种方式也最好仅仅只是用于测试，因为它的优先级非常高，并且影响全局。使用也很简单：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ export LD_PRELOAD=./libtest.so</span><br><span class="line">$ ./main</span><br></pre></td></tr></table></figure></p><p>为了避免影响后面的验证，这里取消设置该环境变量：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">unset LD_PREALOD</span><br></pre></td></tr></table></figure></p><h2 id="查找rpath路径"><a href="#查找rpath路径" class="headerlink" title="查找rpath路径"></a>查找rpath路径</h2><p>上面的情况是找不到动态库，那么它首先会去rpath指定路径去查找，这需要在编译时指定：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">$ gcc test.c -fPIC -shared -o libtest.so -L. -ltest1 -Wl,-rpath $(pwd)</span><br><span class="line">$ gcc -o main main.c -L . -ltest -Wl,-rpath $(pwd)</span><br><span class="line">$ ./main</span><br><span class="line">I am test；hello，编程珠玑</span><br><span class="line">test1,needed by test</span><br></pre></td></tr></table></figure></p><p>也就是说，如果我们编译时指定了路径，就可以找到了，但是这些信息被写入到了ELF文件中。</p><h2 id="LD-LIBRARY-PATH环境变量"><a href="#LD-LIBRARY-PATH环境变量" class="headerlink" title="LD_LIBRARY_PATH环境变量"></a>LD_LIBRARY_PATH环境变量</h2><p>另外也可以通过这个环境变量来设置要搜索库的路径。<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ gcc -o main main.c -L . -ltest</span><br><span class="line">$ export LD_LIBRARY_PATH=./</span><br><span class="line">$ ./main</span><br></pre></td></tr></table></figure></p><p>这样运行也是没有问题的。</p><p>同样，为了避免对后面测试产生影响，取消设置该环境变量：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">unset LD_LIBRARY_PATH</span><br></pre></td></tr></table></figure></p><h2 id="etc-ld-so-conf中的路径"><a href="#etc-ld-so-conf中的路径" class="headerlink" title="/etc/ld.so.conf中的路径"></a>/etc/ld.so.conf中的路径</h2><p>我的机器上这个文件的内容如下：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ cat /etc/ld.so.conf</span><br><span class="line">include /etc/ld.so.conf.d/*.conf</span><br><span class="line">$ ls /etc/ld.so.conf.d/</span><br><span class="line">fakeroot-x86_64-linux-gnu.conf  libc.conf  x86_64-linux-gnu.conf</span><br></pre></td></tr></table></figure></p><p>所以它实际指的是/etc/ld.so.conf.d/目录下所有conf路径包含路径，打开其中一个libc.conf，它里面包含的路径为：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ /usr/local/lib</span><br></pre></td></tr></table></figure></p><p>既然如此，我们把前面的libtest.so复制到该目录下(可能需要sudo权限)：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">$ sudo cp libtest.so /usr/local/lib</span><br><span class="line">$ sudo ldconfig</span><br><span class="line">$ ./main</span><br><span class="line">I am test；hello，编程珠玑</span><br><span class="line">test1,needed by test</span><br></pre></td></tr></table></figure></p><p>注意，这里拷贝完成后，需要执行ldconfig，它会更新相应的共享库，以便可执行程序能够找到。实际上，执行完成后，你确实就能在/etc/ld.so.cache文件中找到：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ grep -a libtest.so /etc/ld.so.cache</span><br></pre></td></tr></table></figure></p><p>同样，为了影响后面测试，记得删除：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">rm /usr/local/lib/libtest.so</span><br></pre></td></tr></table></figure></p><p>实际上这里是先从/etc/ld.so.cache中的路径查找，然后再从ld.so.conf的路径中查找。后面我们可以通过命令看到。</p><h2 id="usr-lib，-lib"><a href="#usr-lib，-lib" class="headerlink" title="/usr/lib，/lib/"></a>/usr/lib，/lib/</h2><p>当然了，如果以上路径都没有，最终还会在lib或/usr/lib下找，为了验证，我们将库拷贝到/lib目录下<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ cp libtest.so /lib</span><br><span class="line">$ ./main</span><br><span class="line">I am test；hello，编程珠玑</span><br><span class="line">test1,needed by test</span><br></pre></td></tr></table></figure></p><p>同样能正常运行。</p><h2 id="小结-1"><a href="#小结-1" class="headerlink" title="小结"></a>小结</h2><p>小结一下，动态库的搜索顺序如下：</p><ul><li>LD_PRELOAD环境变量指定库路径</li><li>-rpath链接时指定路径</li><li>LD_LIBRARY_PATH环境变量设置路径</li><li>/etc/ld.so.conf配置文件指定路径</li><li>默认共享库路径，/usr/lib，lib</li></ul><p>以上这些查找路径你很容易来验证它们的优先级，简单的做法就是这几个位置分别放置同名不同作用的库，来看看它到底先使用哪个路径下的库，可自行尝试。</p><h2 id="LD-DEBUG"><a href="#LD-DEBUG" class="headerlink" title="LD_DEBUG"></a>LD_DEBUG</h2><p>这个环境通常用来调试。例如，查看整个装载过程：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ LD_DEBUG=files ./main</span><br></pre></td></tr></table></figure></p><p>或者查看依赖的库的查找过程：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ LD_DEBUG=libs ./main</span><br><span class="line">      3557:find library=libtest.so [0]; searching</span><br><span class="line">      3557: search cache=/etc/ld.so.cache</span><br><span class="line">      3557:  trying file=/usr/local/lib/libtest.so</span><br></pre></td></tr></table></figure></p><p>另外还可以显示符号的查找过程：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ LD_DEBUG=symbols ./main</span><br></pre></td></tr></table></figure></p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>了解动态库的搜索路径，可以在开发中很好的帮助你定位找不到库的问题，同时LD_DEBUG环境变量也能够很好的帮助你调试，例如查看库搜索的路径，显示符号的查找过程等等。</p><p>虽然程序运行能够有多种途径获取动态库路径，但是并不是每种方式都合适，有的方式甚至完全不该用，但这超出了本文的讨论范围了。有兴趣的也可以点击阅文原文，查看《<a href="http://xahlee.info/UnixResource_dir/_/ldpath.html" target="_blank" rel="noopener">Why LD_LIBRARY_PATH is bad</a>》</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;我们都知道，二进制程序文件和动态库通常并不是完全绑定在一起发布的，那么可执行程序运行时，是如何找到动态库的呢？&lt;br&gt;
    
    </summary>
    
      <category term="编译链接" scheme="https://www.yanbinghu.com/categories/%E7%BC%96%E8%AF%91%E9%93%BE%E6%8E%A5/"/>
    
    
      <category term="C" scheme="https://www.yanbinghu.com/tags/C/"/>
    
      <category term="链接" scheme="https://www.yanbinghu.com/tags/%E9%93%BE%E6%8E%A5/"/>
    
  </entry>
  
  <entry>
    <title>利用强弱符号制作插件库</title>
    <link href="https://www.yanbinghu.com/2021/02/17/26277.html"/>
    <id>https://www.yanbinghu.com/2021/02/17/26277.html</id>
    <published>2021-02-17T03:00:00.000Z</published>
    <updated>2021-02-17T02:37:31.392Z</updated>
    
    <content type="html"><![CDATA[<p>在《<a href="https://www.yanbinghu.com/2021/02/17/26301.html">什么是强符号和弱符号</a>》中简单介绍了强弱符号，那么强弱符号的性质有什么用呢？</p><a id="more"></a><p>还记得在《<a href="https://www.yanbinghu.com/2021/02/17/26301.html">什么是强符号和弱符号</a>》中提到的链接原则吗？</p><ul><li>当有强符号和弱符号时，选择使用强符号</li></ul><p>那么我们正可以利用这个原则做以下事情：</p><ul><li>定义为弱符号，如果是弱符号，使用默认行为</li><li>如果链接了库，是强符号，则使用外部定义行为</li></ul><p>以此来实现一个类似插件的功能。通俗一点说：</p><ul><li>当没有插件时，使用默认行为</li><li>链接了插件时，使用插件的功能</li></ul><h2 id="原理和示例"><a href="#原理和示例" class="headerlink" title="原理和示例"></a>原理和示例</h2><p>其原理也非常简单，举个例子：</p><ul><li>外部引用弱符号</li><li>如果符号地址为0，则说明外部没有链接插件库，未有强符号，走默认流程</li><li>如果符号地址不为0，则说明链接了插件库，执行插件库的功能。</li></ul><p>示例程序如下：<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 来源：公众号【编程珠玑】</span></span><br><span class="line"><span class="comment">// 作者：守望先生</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span><span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line">__attribute__((weak)) <span class="function"><span class="keyword">void</span> <span class="title">my_print</span><span class="params">()</span></span>;</span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">test_print</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="comment">// 如果是强符号，说明链接了外部插件，使用外部定义</span></span><br><span class="line">    <span class="keyword">if</span>(my_print)</span><br><span class="line">    &#123;</span><br><span class="line">        my_print();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="comment">// 弱符号，走默认逻辑</span></span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">"this is weak print\n"</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    test_print();</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>上面的test_print函数是弱符号，在没有其他地方定义的情况下，也是能够正常编译运行的：<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ gcc -o main main.c</span><br><span class="line">$ ./main</span><br><span class="line"><span class="keyword">this</span> is weak print</span><br></pre></td></tr></table></figure></p><p>观察可执行文件：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ nm main |grep my_print</span><br><span class="line">           w my_print</span><br></pre></td></tr></table></figure></p><p>通过nm命令我们也可以知道test_print是弱符号，它前面的修饰字符是W，代表weak。</p><h2 id="插件库"><a href="#插件库" class="headerlink" title="插件库"></a>插件库</h2><p>前面的示例程序已经能否工作了，如何让它能否支持插件库呢？或者说，如何让它支持外部的插件功能呢？</p><p>关于制作库（静态库或动态库制作可以参考《<a href="https://www.yanbinghu.com/2019/07/10/23906.html">手把手教你制作静态库</a>》）</p><p>这里以静态库为例：<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// print_plugin.c</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span><span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">my_print</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"this is plugin print\n"</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>制作静态库：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ gcc -c print_plugin.c</span><br><span class="line">$ ar -rcs libprint_plugin.a print_plugin.o</span><br></pre></td></tr></table></figure></p><h2 id="链接插件库"><a href="#链接插件库" class="headerlink" title="链接插件库"></a>链接插件库</h2><p>现在重新编译main程序，并使用插件库：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">$ gcc -o main main.c -L./ -lprint_plugin</span><br><span class="line">$ gcc  -o main  main.c  -L. -Wl,--whole-archive -lprint_plugin -Wl,--no-whole-archive</span><br><span class="line">$ nm main |grep my_print</span><br><span class="line">000000000000067a T my_print</span><br><span class="line">$ ./main</span><br><span class="line">this is plugin print</span><br></pre></td></tr></table></figure></p><p>需要注意的是，这里在链接插件库之前，需要加上：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">-Wl,--whole-archive</span><br></pre></td></tr></table></figure></p><p>该选项会将插件库中所有符号都链接进来，若非如此，在main.c中已经有了my_print符号，将不会链接进来，而在此之后，又要将该选项恢复。最终我们可以通过nm命令看到my_print符号已经不再是W了。也就看到了最后：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">this is plugin print</span><br></pre></td></tr></table></figure></p><p>的打印了。</p><p>也就实现了我们所谓插件的功能，换句话说，可以对目标程序进行功能的裁剪或者增加。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>由于以下几点原因，我们可以自己做一些支持插件库的程序：</p><ul><li>1.重复强弱符号同存在时，使用强符号</li><li>2.弱符号链接不存在时，不会报错</li><li>3.未链接的外部符号，地址为0，可通过判断避免访问非法地址</li></ul><p>再结合前面的例子分别解释一下：<br>1.这一点在《<a href="https://www.yanbinghu.com/2021/02/17/26301.html">什么是强符号和弱符号</a>》一文中已经有解释说明了<br>2.在开始的程序中，即便没有链接插件库，程序也可以正常编译链接通过，而不会报错<br>3.没有链接插件库时，由于其函数地址为0，因此，我们程序内判断，if(xxx)，当地址为0时，执行默认的行为语句。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;在《&lt;a href=&quot;https://www.yanbinghu.com/2021/02/17/26301.html&quot;&gt;什么是强符号和弱符号&lt;/a&gt;》中简单介绍了强弱符号，那么强弱符号的性质有什么用呢？&lt;/p&gt;
    
    </summary>
    
      <category term="编译链接" scheme="https://www.yanbinghu.com/categories/%E7%BC%96%E8%AF%91%E9%93%BE%E6%8E%A5/"/>
    
    
      <category term="C" scheme="https://www.yanbinghu.com/tags/C/"/>
    
      <category term="链接" scheme="https://www.yanbinghu.com/tags/%E9%93%BE%E6%8E%A5/"/>
    
  </entry>
  
  <entry>
    <title>什么是强符号和弱符号？</title>
    <link href="https://www.yanbinghu.com/2021/02/17/26301.html"/>
    <id>https://www.yanbinghu.com/2021/02/17/26301.html</id>
    <published>2021-02-17T02:00:00.000Z</published>
    <updated>2021-02-17T02:08:16.244Z</updated>
    
    <content type="html"><![CDATA[<p>C/C++中的符号是什么？什么是强符号？什么又是弱符号？<br><a id="more"></a><br>先问一个问题，如果全局变量或者函数重复定义了，会发生什么？</p><p>可能有些朋友第一反应是，那肯定是编译不过喽：<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 来源：公众号【编程珠玑】</span></span><br><span class="line"><span class="comment">// 作者：守望先生</span></span><br><span class="line"><span class="comment">// fun.c</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span><span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">fun</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"编程珠玑\n"</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// main.c</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span><span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">func</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"公众号\n"</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    func();</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>编译：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">$ gcc -o main main.c fun.c</span><br><span class="line">/tmp/ccKeACRk.o: In function `fun&apos;:</span><br><span class="line">fun.c:(.text+0x0): multiple definition of `fun&apos;</span><br><span class="line">/tmp/cc4ezgqh.o:main.c:(.text+0x0): first defined here</span><br><span class="line">collect2: error: ld returned 1 exit status</span><br></pre></td></tr></table></figure></p><p>可以看到这里报错了，因为fun重复定义了。</p><p>但是重复定义就会报错，会编译不过吗？不全是！</p><p>再看下面的代码：<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 来源：公众号【编程珠玑】</span></span><br><span class="line"><span class="comment">// 作者：守望先生</span></span><br><span class="line"><span class="comment">//var.c</span></span><br><span class="line"><span class="keyword">int</span> num;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">change</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    num = <span class="number">1023</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//main.c</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span><span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">change</span><span class="params">()</span></span>;</span><br><span class="line"><span class="keyword">int</span> num = <span class="number">1024</span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"before:num is %d\n"</span>, num);</span><br><span class="line">    change();</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"after:num is %d\n"</span>, num);</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>输出结果：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">before:num is 1024 </span><br><span class="line">after:num is 1023</span><br></pre></td></tr></table></figure></p><p>从结果中可以看到，虽然num被定义了两次，但是仍然可以编译通过，并且正常运行。这又是为什么呢？</p><h2 id="符号"><a href="#符号" class="headerlink" title="符号"></a>符号</h2><p>在说明今天重点分享的内容之前，先简单了解一下什么是符号。在《<a href="https://www.yanbinghu.com/2018/10/10/27133.html">hello程序是如何变成可执行文件的</a>》中讲到过，ELF文件生成的最后阶段会经历链接，而链接阶段正是基于符号才能完成。每个目标文件都会有一个符号表。而链接过程正是通过符号表中的符号，将不同的目标文件“粘”在一起，形成最后的库或者可执行文件。要查看一个目标文件的符号信息也很容易：<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// symbol.c</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span><span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="keyword">int</span> symbol = <span class="number">1024</span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">func_symbol</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>编译：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ gcc smbol.c #编译</span><br><span class="line">$ nm symbol.o #查看符号信息</span><br><span class="line">0000000000000000 T func_symbol</span><br><span class="line">0000000000000000 D symbol</span><br></pre></td></tr></table></figure></p><p>通过nm命令就可以查看符号信息，这里就有我们的func_symbol函数和全局变量symbol的符号。<br>关于nm的使用，在《<a href="https://www.yanbinghu.com/2019/10/13/54745.html">几个命令了解ELF文件的秘密</a>》也有介绍。<br>除了上面提到的全局符号，目标文件中还有其他符号信息，不过这不是本文关注的重点。</p><h2 id="强符号与弱符号"><a href="#强符号与弱符号" class="headerlink" title="强符号与弱符号"></a>强符号与弱符号</h2><p>对于C/C++语言来说，<font color="red"><strong>编译器默认函数和初始化了的全局变量为强符号，未初始化的全局变量为弱符号</strong></font>。当然也可以通过<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">__attribute__((weak))</span><br></pre></td></tr></table></figure></p><p>来定义一个强符号为弱符号。</p><p>通过下面的例子来看看哪些是强符号，哪些是弱符号：<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span><span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="keyword">int</span> weak; <span class="comment">// 未初始化全局变量，弱符号</span></span><br><span class="line"><span class="keyword">int</span> strong = <span class="number">1024</span>; <span class="comment">// 已初始化全局变量，强符号</span></span><br><span class="line">__attribute__((weak)) <span class="keyword">int</span> weak1 = <span class="number">2222</span>; <span class="comment">// 使用标识修饰的弱符号</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"编程珠玑\n"</span>);</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>注意，<font color="red">这里的强符号与弱符号都是针对定义来说的。</font></p><h2 id="同名时，用哪个？"><a href="#同名时，用哪个？" class="headerlink" title="同名时，用哪个？"></a>同名时，用哪个？</h2><p>对于多重定义，即标题提到的变量重名时，链接器有它的处理规则：</p><ul><li>1.强符号不允许重复</li><li>2.有一个强符号和多个弱符号，使用强符号</li><li>3.多个弱符号，则随意选择一个</li></ul><p>关于第一点，在最开始的例子中你已经见到了，最常见的情况就是你重复定义了变量或者函数等等。</p><p>而第二点也有示例，示例中，虽然定义了两个num，但是var.c中未初始化的num是弱符号，main.c中的num是强符号，这种情况下编译正常。只是最终会使用强符号的num。</p><p>再看一个第三点的例子也是类似，当其中main.c的num无初始化时，也是可以编译过的。这种情况下的误用也就罢了，如果是重复的符号，但是类型不同，问题就更大了，即var.c的内容如下：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">//var.c</span><br><span class="line">double num;</span><br><span class="line">void change()</span><br><span class="line">&#123;</span><br><span class="line">    num = 1023;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>这里的num变成了double，再次编译运行，你会发现意想不到的结果：<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">before:num is <span class="number">1024</span> </span><br><span class="line">after:num is <span class="number">0</span></span><br></pre></td></tr></table></figure></p><p>为什么修改后是0？原因在于double类型的数据存储与int类型数据存储格式不一样（参考《<a href="https://www.yanbinghu.com/2018/09/05/65196.html">对浮点数的一些理解</a>》），且它们占用空间长度都不一样，在本文例子中，double占用8字节，而int占用4字节。</p><p>总之，这不是我们想要的结果，最终的后果可能比我们想象的要严重，要更难发现。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>如非特殊需求，应该尽量避免出现全局变量同名，以免造成意料不到的结果，例如使用变量时最小范围定义，即尽可能避免全局变量，或者使用命名空间（如C++）。</p><p>当然了，强弱符号在某些时候是非常有用的，例如制作库以支持用户自定义的库，这又该怎么做呢？敬请期待下一篇。</p><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><p>参考书籍</p><ul><li>《深入理解计算机系统》</li><li>《程序员的自我修养》</li></ul>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;C/C++中的符号是什么？什么是强符号？什么又是弱符号？&lt;br&gt;
    
    </summary>
    
      <category term="编译链接" scheme="https://www.yanbinghu.com/categories/%E7%BC%96%E8%AF%91%E9%93%BE%E6%8E%A5/"/>
    
    
      <category term="C" scheme="https://www.yanbinghu.com/tags/C/"/>
    
      <category term="链接" scheme="https://www.yanbinghu.com/tags/%E9%93%BE%E6%8E%A5/"/>
    
  </entry>
  
  <entry>
    <title>C语言泛型编程</title>
    <link href="https://www.yanbinghu.com/2020/12/27/17582.html"/>
    <id>https://www.yanbinghu.com/2020/12/27/17582.html</id>
    <published>2020-12-27T08:50:00.000Z</published>
    <updated>2020-12-27T08:56:12.045Z</updated>
    
    <content type="html"><![CDATA[<p>泛型编程（generic programming）是程序设计语言的一种风格或范式。泛型允许程序员在强类型程序设计语言中编写代码时使用一些以后才指定的类型，在实例化时作为参数指明这些类型。<br><a id="more"></a><br>C++支持泛型编程，也就是模板，比如：<br><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 来源：公众号【 编程珠玑】</span></span><br><span class="line"><span class="comment">// 作者：守望先生</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="keyword">template</span> &lt;<span class="class"><span class="keyword">class</span> <span class="title">T</span>&gt;</span></span><br><span class="line"><span class="class"><span class="title">T</span> <span class="title">add</span>(<span class="title">T</span> <span class="title">a</span>,<span class="title">T</span> <span class="title">b</span>)&#123;</span></span><br><span class="line">  T ret = a + b;</span><br><span class="line">  <span class="built_in">std</span>::<span class="built_in">cout</span>&lt;&lt; a &lt;&lt; <span class="string">" + "</span> &lt;&lt; b &lt;&lt;<span class="string">" = "</span> &lt;&lt; ret &lt;&lt; <span class="built_in">std</span>::<span class="built_in">endl</span>;</span><br><span class="line">  <span class="keyword">return</span> ret;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span>&#123;</span><br><span class="line">  add(<span class="number">1</span>,<span class="number">2</span>);  <span class="comment">// 整数相加</span></span><br><span class="line">  add(<span class="number">1.2</span>,<span class="number">2.3</span>); <span class="comment">// 浮点数相加</span></span><br><span class="line">  <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>运行结果：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">1 + 2 = 3</span><br><span class="line">1.2 + 2.3 = 3.5</span><br></pre></td></tr></table></figure></p><p>从上面的结果可以看到，对于调用add函数，如果传入的是整型，则按照整型加法计算，如果是浮点数，则按照浮点数进行加法计算。也就是说，add函数没有针对特定类型（泛型）。</p><p>你同样可以使用<a href="https://www.yanbinghu.com/2018/11/27/34641.html">重载</a>实现上面的功能，但是存在大量重复代码。</p><h2 id="C语言支持泛型编程吗？"><a href="#C语言支持泛型编程吗？" class="headerlink" title="C语言支持泛型编程吗？"></a>C语言支持泛型编程吗？</h2><p>很遗憾，C语言本身不支持真正意义上的泛型编程，但是却在一定程度上可以“实现泛型编程”。</p><h2 id="Generic关键字"><a href="#Generic关键字" class="headerlink" title="_Generic关键字"></a>_Generic关键字</h2><p>_Generic是C11的关键字，通过该关键字可以有一个泛型表达式：<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">_Generic((value). <span class="keyword">int</span>:<span class="string">"int"</span>, <span class="keyword">float</span>:<span class="string">"float"</span>,<span class="keyword">char</span>*:<span class="string">"char*"</span>,<span class="keyword">default</span>:<span class="string">"other type"</span>)</span><br></pre></td></tr></table></figure></p><p>什么意思呢？如果value是int类型，那么表达式的值就是“int”，其他的以此类推。看起来是不是和switch语句有点类似呢？<br>根据这个示例，我们来实现一个功能，打印变量或常量到底是什么类型：<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 来源：公众号【编程珠玑】</span></span><br><span class="line"><span class="comment">// 作者：守望先生</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> TYPE(v) _Generic((v), \</span></span><br><span class="line">    <span class="keyword">int</span>:<span class="string">"int"</span>, \</span><br><span class="line">    <span class="keyword">char</span>:<span class="string">"char"</span>, \</span><br><span class="line">    <span class="keyword">float</span>:<span class="string">"float"</span>, \</span><br><span class="line">    <span class="keyword">double</span>:<span class="string">"double"</span>, \</span><br><span class="line">    <span class="keyword">char</span>*:<span class="string">"char*"</span>, \</span><br><span class="line">    <span class="keyword">default</span>:<span class="string">"other type"</span>)</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"1 + 2 type: %s\n"</span>,TYPE(<span class="number">1</span> + <span class="number">2</span>));</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"1/3 type: %s\n"</span>,TYPE(<span class="number">1</span>/<span class="number">3</span>));</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"2/3 type: %s\n"</span>,TYPE((<span class="keyword">float</span>)<span class="number">2</span>/<span class="number">3</span>));</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"xxx type: %s\n"</span>,TYPE(<span class="string">"xxx"</span>));</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>这里为了方便使用，我们通过define关键字，将泛型表达式简化。</p><p>运行结果：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">1 + 2 type: int</span><br><span class="line">1/3 type: int</span><br><span class="line">2/3 type: float                                                        </span><br><span class="line">xxx type: char*</span><br></pre></td></tr></table></figure></p><p>可以看到通过TYPE就可以获得表达式的结果类型，<strong>这对初学者来说，可真是福音了</strong>。</p><h2 id="泛型算法"><a href="#泛型算法" class="headerlink" title="泛型算法"></a>泛型算法</h2><p>既然C语言有_Generic关键字了，那么我们尝试实现开头C++示例代码中的加法。看过上面的例子后，相信你已经会了：<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 来源：公众号【编程珠玑】</span></span><br><span class="line"><span class="comment">// 作者：守望先生</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="comment">// int类型加法</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">addI</span><span class="params">(<span class="keyword">int</span> a, <span class="keyword">int</span> b)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"%d + %d = %d\n"</span>,a,b, a + b );</span><br><span class="line">    <span class="keyword">return</span> (a + b);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// double类型加法</span></span><br><span class="line"><span class="function"><span class="keyword">double</span> <span class="title">addF</span><span class="params">(<span class="keyword">double</span> a, <span class="keyword">double</span> b)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"%f + %f = %f\n"</span>,a,b, a + b );</span><br><span class="line">    <span class="keyword">return</span> (a + b);</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">unsupport</span><span class="params">(<span class="keyword">int</span> a,<span class="keyword">int</span> b)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"unsupport type\n"</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> ADD(a,b) _Generic((a), \</span></span><br><span class="line">    <span class="keyword">int</span>:addI(a,b),\</span><br><span class="line">    <span class="keyword">double</span>:addF(a,b), \</span><br><span class="line">    <span class="keyword">default</span>:unsupport(a,b))</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    ADD(<span class="number">1</span> , <span class="number">2</span>);</span><br><span class="line">    ADD(<span class="number">1.1</span>,<span class="number">2.2</span>);</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>观察上面的代码，我们注意到：</p><ul><li>在这里，我们需要定义两种类型的加法（实际上，通过C++的模板，由编译器帮我们完成了这件事），由于C语言中并不支持<a href="https://www.yanbinghu.com/2018/11/27/34641.html">重载</a>，因此两个加法的函数名不一样。</li><li>由于涉及参数有两个，在做类型判断时，如果两个参数不一致，可能仍然存在编译问题</li><li>调用者无需区分被加对象是什么类型，都可以统一使用ADD</li></ul><h2 id="C99的tgmath-h"><a href="#C99的tgmath-h" class="headerlink" title="C99的tgmath.h"></a>C99的tgmath.h</h2><p>前面说到，_Generic关键字在C11中才有，那么C99怎么办呢？实际上，tgmath.h中提供了一些泛型类型宏，如果math.h的函数中定义了float，double和long double版本，tgmath就会提供一个泛型类型宏。效果和前面的例子一样，举个例子：<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 来源：公众号【编程珠玑】</span></span><br><span class="line"><span class="comment">// 作者：守望先生</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;tgmath.h&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">float</span> f = <span class="number">4.0f</span>;</span><br><span class="line">    <span class="keyword">long</span> <span class="keyword">double</span> d = <span class="number">1.44</span>;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"%f\n"</span>,<span class="built_in">sqrt</span>(f)); <span class="comment">// 实际上调用了sqrtf</span></span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"%Lf\n"</span>,<span class="built_in">sqrt</span>(d)); <span class="comment">// 实际上调用了sqrtl</span></span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>编译运行结果：<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">2.000000</span></span><br><span class="line"><span class="number">1.200000</span></span><br></pre></td></tr></table></figure></p><p>但是不得不说，tgmath中提供的泛型宏也是有限的。</p><h2 id="void-指针"><a href="#void-指针" class="headerlink" title="void *指针"></a>void *指针</h2><p>众所周知，C语言中void *指针是一种无类型指针，从这点看，也可以算是泛型指针了。而它的使用在C语言中是非常常见的，举例来说，在《<a href="https://www.yanbinghu.com/2019/01/03/3593.html">函数指针</a>》中，我们介绍了快速排序接口的使用，它的函数声明是这样的：<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdlib.h&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">qsort</span><span class="params">(<span class="keyword">void</span> *base, <span class="keyword">size_t</span> nmemb, <span class="keyword">size_t</span> size,</span></span></span><br><span class="line"><span class="function"><span class="params">                  <span class="keyword">int</span> (*compar)(<span class="keyword">const</span> <span class="keyword">void</span> *, <span class="keyword">const</span> <span class="keyword">void</span> *))</span></span>;</span><br></pre></td></tr></table></figure></p><p>库函数qsort实际上就是泛型排序算法了，它可以针对任何类型的数据进行排序。当然有一个前提，就是你需要按照它的协议，实现一个compar函数，用于比较大小。</p><p>像这样类似的例子，C语言中还有很多，不过相比于其他语言，如C++中的模板，这种所谓的泛型，确实有些小巫见大巫了。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>C语言语法上本身基本不支持泛型编程，但是借助_Generic关键字和一些手段，可以实现泛型编程。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;泛型编程（generic programming）是程序设计语言的一种风格或范式。泛型允许程序员在强类型程序设计语言中编写代码时使用一些以后才指定的类型，在实例化时作为参数指明这些类型。&lt;br&gt;
    
    </summary>
    
      <category term="C" scheme="https://www.yanbinghu.com/categories/C/"/>
    
    
      <category term="C" scheme="https://www.yanbinghu.com/tags/C/"/>
    
  </entry>
  
  <entry>
    <title>性能优化-使用高效内存分配器</title>
    <link href="https://www.yanbinghu.com/2020/12/20/39641.html"/>
    <id>https://www.yanbinghu.com/2020/12/20/39641.html</id>
    <published>2020-12-20T14:00:00.000Z</published>
    <updated>2020-12-20T13:40:25.890Z</updated>
    
    <content type="html"><![CDATA[<p>性能优化是一个常有的事情，通常来说</p><ul><li>不要过早优化-当你没有性能问题时，不需要过早考虑优化，当然对于一些代价很小，收益却很大的手段可以考虑做进来，例如最常见的就是根据业务需求选择合适的数据结构。</li><li>不要过度优化。优化都是有目标的，比如你需要达到多少TPS，那么你按照这个目标去优化即可，有些优化虽然能否提升性能，但可能对代码的可维护性造成破坏。</li></ul><p>本人对此没有过多涉猎，仅分享工作中接触到的一些内存。<br><a id="more"></a></p><h2 id="内存性能问题"><a href="#内存性能问题" class="headerlink" title="内存性能问题"></a>内存性能问题</h2><p>有很多方面会造成性能问题，例如：</p><ul><li>业务流程设计不合理，导致很多没有必要的计算</li><li>数据结构选择不合适</li><li>缓存使用不当</li></ul><h2 id="示例"><a href="#示例" class="headerlink" title="示例"></a>示例</h2><p>假设你已经通过《<a href="https://www.yanbinghu.com/2020/08/22/63600.html">perf:一个命令发现性能问题</a>》中的方法或者使用profiler分析，已经发现内存分配是性能瓶颈：<br><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 来源：公众号【编程珠玑】</span></span><br><span class="line"><span class="comment">// 作者：守望先生</span></span><br><span class="line"><span class="comment">// malloc.cc</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;thread&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;vector&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdlib.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;string.h&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">GetMemory</span><span class="params">()</span></span>&#123;</span><br><span class="line">  <span class="keyword">for</span>(<span class="keyword">int</span> i = <span class="number">0</span>;i &lt; <span class="number">100000000</span>; i++)&#123;</span><br><span class="line">    <span class="keyword">void</span> *p = <span class="built_in">malloc</span>(<span class="number">1024</span>);</span><br><span class="line">    <span class="keyword">if</span>(<span class="literal">NULL</span> != p)&#123;</span><br><span class="line">      <span class="built_in">free</span>(p);</span><br><span class="line">      p = <span class="literal">NULL</span>;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span>&#123;</span><br><span class="line">  <span class="built_in">std</span>::<span class="built_in">vector</span>&lt;<span class="built_in">std</span>::thread&gt; th;</span><br><span class="line">  <span class="keyword">int</span> nr_threads = <span class="number">10</span>;</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; nr_threads; ++i) &#123;</span><br><span class="line">    th.push_back(<span class="built_in">std</span>::thread(GetMemory));</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">for</span>(<span class="keyword">auto</span> &amp;t : th)&#123;</span><br><span class="line">    t.join();</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>代码非常简单，仅仅是不断分配内存而已。</p><p>编译并尝试分配十亿次：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">$ g++ -g -o malloc malloc.cc -lpthread</span><br><span class="line">$ time ./malloc </span><br><span class="line">real0m8.677s</span><br><span class="line">user0m29.409s</span><br><span class="line">sys0m0.029s</span><br></pre></td></tr></table></figure></p><p>分配十亿次内存，使用时间大概17s左右。另外一个终端使用perf查看情况：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">$ perf top -p `pidof malloc`</span><br><span class="line">  52.92%  libc-2.27.so  [.] cfree@GLIBC_2.2.5</span><br><span class="line">  31.94%  libc-2.27.so  [.] malloc</span><br><span class="line">   8.82%  malloc        [.] GetMemory</span><br><span class="line">   3.45%  malloc        [.] free@plt</span><br><span class="line">   2.51%  malloc        [.] malloc@plt</span><br><span class="line">   0.03%  [kernel]      [k] prepare_exit_to_usermode</span><br><span class="line">   0.01%  [kernel]      [k] psi_task_change</span><br><span class="line">   0.01%  [kernel]      [k] native_irq_return_iret</span><br><span class="line">   0.01%  [kernel]      [k] __update_load_avg_cfs_rq</span><br><span class="line">   0.01%  [kernel]      [k] __update_load_avg_se</span><br><span class="line">   0.01%  [kernel]      [k] update_curr</span><br><span class="line">   0.01%  [kernel]      [k] native_write_msr</span><br><span class="line">   0.01%  [kernel]      [k] __schedule</span><br><span class="line">   0.01%  [kernel]      [k] native_read_msr</span><br><span class="line">   0.01%  [kernel]      [k] read_tsc</span><br><span class="line">   0.01%  [kernel]      [k] interrupt_entry</span><br><span class="line">   0.01%  [kernel]      [k] update_load_avg</span><br><span class="line">   0.01%  [kernel]      [k] swapgs_restore_regs_and_return_to_usermode</span><br><span class="line">   0.01%  [kernel]      [k] reweight_entity</span><br><span class="line">   0.01%  [kernel]      [k] switch_fpu_return</span><br><span class="line">   0.01%  [kernel]      [k] perf_event_task_tick</span><br></pre></td></tr></table></figure></p><p>从结果可以看到，大部分CPU耗费在了内存的申请和释放。</p><p>怎么办呢？第一要考虑的做法不是如何提升它，而是它能否避免？比如内存复用？而非反复申请？<br>比如使用内存池？但是要自己写一个稳定的内存池又需要耗费很大的精力了。怎么办呢？</p><h2 id="性能更好的库"><a href="#性能更好的库" class="headerlink" title="性能更好的库"></a>性能更好的库</h2><p>实际上这就引出了性能优化的一种常见方法-使用性能更好的库。那么在内存分配方面，有更好的库吗？自己又不能写出一个比libc还厉害的库，就只能用用开源的库，才能维持得了写代码的生活。</p><p>目前常见的性能比较好的内存分配库有</p><ul><li>tcmalloc-谷歌开发的内存分配库</li><li>jemalloc</li></ul><p>在自己编译使用redis的时候，其实你能看到它们的身影：<br><figure class="highlight makefile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Backwards compatibility for selecting an allocator</span></span><br><span class="line"><span class="keyword">ifeq</span> (<span class="variable">$(USE_TCMALLOC)</span>,yes)</span><br><span class="line">MALLOC=tcmalloc</span><br><span class="line"><span class="keyword">endif</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">ifeq</span> (<span class="variable">$(USE_TCMALLOC_MINIMAL)</span>,yes)</span><br><span class="line">MALLOC=tcmalloc_minimal</span><br><span class="line"><span class="keyword">endif</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">ifeq</span> (<span class="variable">$(USE_JEMALLOC)</span>,yes)</span><br><span class="line">MALLOC=jemalloc</span><br><span class="line"><span class="keyword">endif</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">ifeq</span> (<span class="variable">$(USE_JEMALLOC)</span>,no)</span><br><span class="line">MALLOC=libc</span><br><span class="line"><span class="keyword">endif</span></span><br></pre></td></tr></table></figure></p><h2 id="如何使用"><a href="#如何使用" class="headerlink" title="如何使用"></a>如何使用</h2><p>这里以tcmalloc为例，看一下如何使用该库替换libc中的malloc。tcmalloc使用了thread cache，小块的内存分配都可以从cache中分配。多线程分配内存的情况下，可以减少锁竞争。​</p><h2 id="获取"><a href="#获取" class="headerlink" title="获取"></a>获取</h2><p>你可以通过源码编译获取，github地址：<a href="https://github.com/google/tcmalloc.git" target="_blank" rel="noopener">https://github.com/google/tcmalloc.git</a><br>不过它需要使用bazel进行构建编译，有兴趣的可以自行尝试。</p><p>也可以直接安装：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ apt-get install -y libtcmalloc-minimal4</span><br></pre></td></tr></table></figure></p><p>安装位置查看：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">$ ldconfig -p | grep tcmalloc</span><br><span class="line">libtcmalloc_minimal_debug.so.4 (libc6,x86-64) =&gt; /usr/lib/x86_64-linux-gnu/libtcmalloc_minimal_debug.so.4</span><br><span class="line">libtcmalloc_minimal.so.4 (libc6,x86-64) =&gt; /usr/lib/x86_64-linux-gnu/libtcmalloc_minimal.so.4</span><br><span class="line">libtcmalloc_debug.so.4 (libc6,x86-64) =&gt; /usr/lib/x86_64-linux-gnu/libtcmalloc_debug.so.4</span><br><span class="line">libtcmalloc_and_profiler.so.4 (libc6,x86-64) =&gt; /usr/lib/x86_64-linux-gnu/libtcmalloc_and_profiler.so.4</span><br><span class="line">libtcmalloc.so.4 (libc6,x86-64) =&gt; /usr/lib/x86_64-linux-gnu/libtcmalloc.so.4</span><br></pre></td></tr></table></figure></p><h2 id="LD-PRELOAD"><a href="#LD-PRELOAD" class="headerlink" title="LD_PRELOAD"></a>LD_PRELOAD</h2><p>这种方式在自己测试的时候非常方便，只需要：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ export LD_PRELOAD=/path/to/tcmalloc.so</span><br></pre></td></tr></table></figure></p><p>导入环境变量，指定库路径即可。注意这里的/path/to更换成你的tcmalloc实际的路径。运行的时候，tcmalloc库就会被首先被使用了。</p><h2 id="直接链接"><a href="#直接链接" class="headerlink" title="直接链接"></a>直接链接</h2><p>这种方法就和普通库的使用没有什么区别了，链接使用就完事了。相关文章《<a href="https://www.yanbinghu.com/2019/07/10/23906.html">静态库的制作与使用</a>》</p><h2 id="效果"><a href="#效果" class="headerlink" title="效果"></a>效果</h2><p>我们使用新的库，再进行10亿次的内存分配试试：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ time ./malloc</span><br><span class="line">real0m7.152s</span><br><span class="line">user0m27.997s</span><br><span class="line">sys0m0.032s</span><br></pre></td></tr></table></figure></p><p>可以看到要使用的时间少了些。当然，这里的对比严格来说不是很严禁，首先这里内存分配大小比较单一，并且仅有内存分配，而没有其他处理，真正是否有效果，还是要根据实际业务程序的情况来判断。当然，整体来说，tcmalloc的效果要比libc的malloc分配内存要高效。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>当你的程序中存在大量的内存分配（例如C++频繁使用string），那么可以考虑使用性能更好的内存分配库了。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;性能优化是一个常有的事情，通常来说&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;不要过早优化-当你没有性能问题时，不需要过早考虑优化，当然对于一些代价很小，收益却很大的手段可以考虑做进来，例如最常见的就是根据业务需求选择合适的数据结构。&lt;/li&gt;
&lt;li&gt;不要过度优化。优化都是有目标的，比如你需要达到多少TPS，那么你按照这个目标去优化即可，有些优化虽然能否提升性能，但可能对代码的可维护性造成破坏。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;本人对此没有过多涉猎，仅分享工作中接触到的一些内存。&lt;br&gt;
    
    </summary>
    
      <category term="性能优化" scheme="https://www.yanbinghu.com/categories/%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/"/>
    
    
      <category term="性能优化" scheme="https://www.yanbinghu.com/tags/%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/"/>
    
  </entry>
  
  <entry>
    <title>scanf高级用法</title>
    <link href="https://www.yanbinghu.com/2020/12/12/46629.html"/>
    <id>https://www.yanbinghu.com/2020/12/12/46629.html</id>
    <published>2020-12-12T09:00:00.000Z</published>
    <updated>2020-12-13T08:53:28.882Z</updated>
    
    <content type="html"><![CDATA[<p>不知道你有没有看到过scanf下面这样的用法：<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 来源：公众号【编程珠玑】</span></span><br><span class="line"><span class="comment">// 作者：守望先生</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span><span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">char</span> a[<span class="number">128</span>] = &#123;<span class="number">0</span>&#125;;</span><br><span class="line">    <span class="built_in">scanf</span>(<span class="string">"%[0-9]"</span>,a);</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"%s\n"</span>,a);</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>上面的代码什么意思呢？试一下几个输入输出：<br><a id="more"></a><br>示例0：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">输入：13579</span><br><span class="line">输出：13579</span><br></pre></td></tr></table></figure></p><p>示例1：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">输入:121abc</span><br><span class="line">输出:121</span><br></pre></td></tr></table></figure><p>示例2：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">输入:shouwang123nb455</span><br><span class="line">输出:</span><br></pre></td></tr></table></figure></p><p>注意：这里输出不是123，如果想要输出123怎么办？请看后面丢弃特定字符部分。</p><p>看到这里，估计你已经看清套路了，没错，[0-9]表示scanf只读取0-9的字符，而如果遇到不在该范围内的字符，则停止，不再继续读取，就是前面我们看到的示例情况了。</p><h2 id="scanf的"><a href="#scanf的" class="headerlink" title="scanf的["></a>scanf的[</h2><p>scanf函数中，还有一个不常被人注意的，就是[了。它用来扫描特定的字符集。如果它以^开头，表示扫描除了字符集以外的所有字符，否则就是前面我们看到的，只扫描读取指定字符。</p><h2 id="scanf读取空字符"><a href="#scanf读取空字符" class="headerlink" title="scanf读取空字符"></a>scanf读取空字符</h2><p>我们都知道，scanf在读取内容的时候，会跳过空字符，比如：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">char</span> s[<span class="number">128</span>] = &#123;<span class="number">0</span>&#125;;</span><br><span class="line"><span class="built_in">scanf</span>(<span class="string">"%s"</span>,s);</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"%s\n"</span>,s);</span><br></pre></td></tr></table></figure><p>假设输入为：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">bianchengzhuji</span><br></pre></td></tr></table></figure></p><p>那么输出将会是：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">bianchengzhuji</span><br></pre></td></tr></table></figure></p><p>注意，前面的空字符并没有读入到字符串s中，而是被跳过了。</p><p>那如果要读取空字符怎么办？很简单：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">scanf(&quot;%[^\n]&quot;,s);</span><br></pre></td></tr></table></figure></p><p>这里的意思就是说，除了换行符，其他字符都读入，也就是说前面的空字符也会被读取，就达到了我们的目的了。</p><h2 id="忽略开头的换行符"><a href="#忽略开头的换行符" class="headerlink" title="忽略开头的换行符"></a>忽略开头的换行符</h2><p>如果我们一开始就按回车，你会发现，s什么都没有读入，如何忽略开始的换行呢？像下面这样就可以了：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 来源：公众号【编程珠玑】</span></span><br><span class="line"><span class="comment">// 作者：守望先生</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span><span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">char</span> a[<span class="number">10</span>] = &#123;<span class="number">0</span>&#125;;</span><br><span class="line">    <span class="built_in">scanf</span>(<span class="string">"%*[\n]%[^\n]"</span>,a);</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"%s\n"</span>,a);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>输入输出示例：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">输入:[回车][回车]abc</span><br><span class="line">输出:abc</span><br></pre></td></tr></table></figure></p><p>输入时，按下两次回车，再输入其他字符，则最终会读取其他字符，而忽略开头的回车换行。我们知道，在scanf中，*是跳过相应的字符项，比如，跳过开头的两个数字：<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 来源：公众号【编程珠玑】</span></span><br><span class="line"><span class="comment">// 作者：守望先生</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span><span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> third = <span class="number">0</span>;</span><br><span class="line">    <span class="built_in">scanf</span>(<span class="string">"%*d %*d %d"</span>,&amp;third);</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"%d\n"</span>,third);</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>输入：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">111 222 333</span><br></pre></td></tr></table></figure></p><p>输出：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">333</span><br></pre></td></tr></table></figure></p><p>scanf会跳过前面的111和222，则会读取333，<strong>这个功能在读取文件获取特定列内容的时候很有用</strong>。同理，在前面的例子中%*[\n]即表示跳过换行，<sup><a href="#fn_\\n" id="reffn_\\n">\\n</a></sup>则读取任意字符，直到遇到换行。</p><h2 id="丢弃特定字符"><a href="#丢弃特定字符" class="headerlink" title="丢弃特定字符"></a>丢弃特定字符</h2><p>最开始的例子中，如果开头是字母，即便想读取数字，也读取不到，那么如何跳过开头的字母呢？仿照刚刚讲的：<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 来源：公众号【编程珠玑】</span></span><br><span class="line"><span class="comment">// 作者：守望先生</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span><span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">char</span> a[<span class="number">128</span>] = &#123;<span class="number">0</span>&#125;;</span><br><span class="line">    <span class="built_in">scanf</span>(<span class="string">"%*[a-zA-Z]%[0-9]"</span>,a);</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"%s\n"</span>,a);</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>这样，开头的字母就会被丢弃。</p><h2 id="读取指定长度的任意字符"><a href="#读取指定长度的任意字符" class="headerlink" title="读取指定长度的任意字符"></a>读取指定长度的任意字符</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">char s[8] = &#123;0&#125;;</span><br><span class="line">scanf(&quot;%7s&quot;,s);</span><br></pre></td></tr></table></figure><p>输入：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">abcdefghij</span><br></pre></td></tr></table></figure></p><p>输出：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">abcdefg</span><br></pre></td></tr></table></figure></p><p>这样可以避免缓冲区溢出。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>scanf读取内容会跳过开头的空白字符，遇到换行符或者不是目标字符时结束读取。当然，你不是没有办法，今天所分享的就是办法。当然了，很多时候，你可能会选择使用fgets，getchar之类的函数，无妨。</p><h2 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h2><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 来源：公众号【编程珠玑】</span></span><br><span class="line"><span class="comment">// 作者：守望先生</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span><span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> a = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">int</span> b = <span class="number">0</span>;</span><br><span class="line">    <span class="built_in">scanf</span>(<span class="string">"%d%d"</span>,&amp;a,&amp;b);</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"%d %d\n"</span>,a,b);</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>输入：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">abcd1234</span><br></pre></td></tr></table></figure></p><p>输出会是什么？为什么？</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;不知道你有没有看到过scanf下面这样的用法：&lt;br&gt;&lt;figure class=&quot;highlight c&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;// 来源：公众号【编程珠玑】&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;// 作者：守望先生&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;#&lt;span class=&quot;meta-keyword&quot;&gt;include&lt;/span&gt;&lt;span class=&quot;meta-string&quot;&gt;&amp;lt;stdio.h&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;function&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;title&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(&lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;function&quot;&gt;&lt;/span&gt;&amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;char&lt;/span&gt; a[&lt;span class=&quot;number&quot;&gt;128&lt;/span&gt;] = &amp;#123;&lt;span class=&quot;number&quot;&gt;0&lt;/span&gt;&amp;#125;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;built_in&quot;&gt;scanf&lt;/span&gt;(&lt;span class=&quot;string&quot;&gt;&quot;%[0-9]&quot;&lt;/span&gt;,a);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;built_in&quot;&gt;printf&lt;/span&gt;(&lt;span class=&quot;string&quot;&gt;&quot;%s\n&quot;&lt;/span&gt;,a);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;number&quot;&gt;0&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;&lt;/p&gt;
&lt;p&gt;上面的代码什么意思呢？试一下几个输入输出：&lt;br&gt;
    
    </summary>
    
      <category term="C" scheme="https://www.yanbinghu.com/categories/C/"/>
    
    
      <category term="C" scheme="https://www.yanbinghu.com/tags/C/"/>
    
  </entry>
  
  <entry>
    <title>API设计原则-避免暴露数据成员</title>
    <link href="https://www.yanbinghu.com/2020/11/07/61329.html"/>
    <id>https://www.yanbinghu.com/2020/11/07/61329.html</id>
    <published>2020-11-07T08:00:00.000Z</published>
    <updated>2020-11-07T08:44:53.926Z</updated>
    
    <content type="html"><![CDATA[<p>之前在《<a href="http://www.yanbinghu.com/2020/11/06/3161.html">PIMPL-隐藏接口实现细节</a>》中介绍了一种隐藏类的私有成员的方法，或者说隐藏接口实现细节的方法-PIMPL。<br>假设提供的接口的入参比较复杂，可能有人会考虑使用结构体作为入参。当你考虑这么做的时候，灾难也将会随之而来……<br><a id="more"></a></p><h2 id="示例："><a href="#示例：" class="headerlink" title="示例："></a>示例：</h2><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 来源：公众号【编程珠玑】</span></span><br><span class="line"><span class="comment">// 作者：守望先生</span></span><br><span class="line"><span class="comment">// api.h</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span><span class="meta-string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">Param</span>&#123;</span></span><br><span class="line">    <span class="keyword">int</span> num;</span><br><span class="line">    <span class="built_in">std</span>::<span class="built_in">string</span> str;</span><br><span class="line">&#125;;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">TestFun</span><span class="params">(<span class="keyword">const</span> Param &amp;param)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// api.cc</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"api.h"</span></span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">TestFun</span><span class="params">(<span class="keyword">const</span> Param &amp;param)</span></span>&#123;</span><br><span class="line">    <span class="built_in">std</span>::<span class="built_in">cout</span>&lt;&lt;<span class="string">"num:"</span>&lt;&lt;param.num&lt;&lt;<span class="string">" str:"</span>&lt;&lt;param.str.c_str()&lt;&lt;<span class="built_in">std</span>::<span class="built_in">endl</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>假设提供TestFun作为一个对外接口，我们编译并制作为静态库：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ g++ -c api.cc -I./</span><br><span class="line">$ ar -rcs libapi.a api.o</span><br></pre></td></tr></table></figure></p><p>关于静态库的制作，请参考《<a href="https://www.yanbinghu.com/2019/07/10/23906.html">Linux下如何制作静态库？</a>》。</p><p>另外一个程序main.cc这么使用它：<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 来源：公众号编程珠玑</span></span><br><span class="line"><span class="comment">// 作者：守望先生</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"api.h"</span></span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span>&#123;</span><br><span class="line">    Param param;</span><br><span class="line">    param.num = <span class="number">10</span>;</span><br><span class="line">    param.str = <span class="string">"24"</span>; </span><br><span class="line">    TestFun(param);</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>编译链接使用：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ g++ -o main main.cc -L./ -lapi -I ./</span><br><span class="line">$ ./main</span><br></pre></td></tr></table></figure></p><p>看起来并没有什么问题，有新的参数，可以直接在Param中增加即可，扩展性也不错。</p><h2 id="问题来了"><a href="#问题来了" class="headerlink" title="问题来了"></a>问题来了</h2><p>目前来看是没有什么问题的，但是假设，还有另外一个库要使用它，例如：<br><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 来源：公众号编程珠玑</span></span><br><span class="line"><span class="comment">// 作者：守望先生</span></span><br><span class="line"><span class="comment">// use_api.h</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span><span class="meta-string">"api.h"</span></span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UseApi</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// use_api.cc</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span><span class="meta-string">"use_api.h"</span></span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">UseApi</span><span class="params">()</span></span>&#123;</span><br><span class="line">    Param param;</span><br><span class="line">    param.num = <span class="number">10</span>;</span><br><span class="line">    param.str = <span class="string">"24"</span>; </span><br><span class="line">    TestFun(param);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>也将它作为静态库：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ g++ -c use_api.cc -I./</span><br><span class="line">$ ar -rcs libuse_api.a use_api.o</span><br></pre></td></tr></table></figure></p><p>这个时候同样主程序会用到我们的原始api，但是却使用了不同的版本，比如，新增了Param中新增了一个字段ext：<br><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 来源：公众号【编程珠玑】</span></span><br><span class="line"><span class="comment">// 作者：守望先生</span></span><br><span class="line"><span class="comment">// api.h</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span><span class="meta-string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">Param</span>&#123;</span></span><br><span class="line">    <span class="keyword">int</span> num;</span><br><span class="line">    <span class="built_in">std</span>::<span class="built_in">string</span> str;</span><br><span class="line">    <span class="built_in">std</span>::<span class="built_in">string</span> ext;</span><br><span class="line">&#125;;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">TestFun</span><span class="params">(<span class="keyword">const</span> Param &amp;param)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// api.cc</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"api.h"</span></span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">TestFun</span><span class="params">(<span class="keyword">const</span> Param &amp;param)</span></span>&#123;</span><br><span class="line">    <span class="built_in">std</span>::<span class="built_in">cout</span>&lt;&lt;<span class="string">"num:"</span>&lt;&lt;param.num&lt;&lt;<span class="string">" str:"</span>&lt;&lt;param.str.c_str()&lt;&lt;<span class="string">" ext:"</span>&lt;&lt;param.ext.c_str()&lt;&lt;<span class="built_in">std</span>::<span class="built_in">endl</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>重新生成静态库：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ g++ -c api.cc -I./</span><br><span class="line">$ ar -rcs libapi.a api.o</span><br></pre></td></tr></table></figure></p><p>这个时候，通过use_api使用api接口，但是链接新的库：<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 来源：公众号编程珠玑</span></span><br><span class="line"><span class="comment">// 作者：守望先生</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"use_api.h"</span></span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span>&#123;</span><br><span class="line">    UseApi();</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>这个时候，再去编译链接，并运行：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ g++ -o main main.cc -I./ -L./ -luse_api -lapi</span><br><span class="line">$ ./main</span><br><span class="line">Segmentation fault (core dumped)</span><br></pre></td></tr></table></figure></p><p>看到没有，喜闻乐见的core dumped了，分析core还会发现，是由于访问非法地址导致的。</p><p>我们再来梳理一下这个过程：</p><ul><li>提供库libapi.a版本A</li><li>libuse_api使用版本A进行编译，使用A版本的头文件</li><li>libapi.a库升级到B版本，其中头文件中增加了字段，并且实现也引用了新的字段</li><li>主程序使用了use_api，但是链接了版本B的libapi.a库</li></ul><p>这个时候，版本B的实现访问了新的字段，还是use_api中还是使用A版本，并没有传入新字段，因此自然会导致非法访问。</p><h2 id="如何解决？"><a href="#如何解决？" class="headerlink" title="如何解决？"></a>如何解决？</h2><p>很简单，不直接暴露成员，而是提供setter和getter，而提供方式和前面提到的PIMPL方法类似。<br><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// api.h</span></span><br><span class="line"><span class="comment">// 来源：公众号编程珠玑</span></span><br><span class="line"><span class="comment">// 作者：守望先生</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span><span class="meta-string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span><span class="meta-string">&lt;memory&gt;</span></span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Param</span>&#123;</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">SetNum</span><span class="params">(<span class="keyword">int</span> num)</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">int</span> <span class="title">GetNum</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">SetStr</span><span class="params">(<span class="keyword">const</span> <span class="built_in">std</span>::<span class="built_in">string</span> &amp;str)</span></span>;</span><br><span class="line">    <span class="function"><span class="built_in">std</span>::<span class="built_in">string</span> <span class="title">GetStr</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">SetExt</span><span class="params">(<span class="keyword">const</span> <span class="built_in">std</span>::<span class="built_in">string</span> &amp;str)</span></span>;</span><br><span class="line">    <span class="function"><span class="built_in">std</span>::<span class="built_in">string</span> <span class="title">GetExt</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line">    Param();</span><br><span class="line">  <span class="keyword">private</span>:</span><br><span class="line">    <span class="class"><span class="keyword">class</span> <span class="title">ParamImpl</span>;</span></span><br><span class="line">    <span class="built_in">std</span>::<span class="built_in">unique_ptr</span>&lt;ParamImpl&gt; param_impl_;</span><br><span class="line">&#125;;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">TestFun</span><span class="params">(<span class="keyword">const</span> Param &amp;param)</span></span>;</span><br></pre></td></tr></table></figure></p><p>在这里头文件中只提供setter和getter，而完全不暴露成员，具体成员的设置在ParamImpl中实现：<br><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// api.cc</span></span><br><span class="line"><span class="comment">// 来源：公众号编程珠玑</span></span><br><span class="line"><span class="comment">// 作者：守望先生</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"api.h"</span></span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Param</span>:</span>:ParamImpl&#123;</span><br><span class="line">  <span class="keyword">public</span>:</span><br><span class="line">    <span class="keyword">int</span> num;</span><br><span class="line">    <span class="built_in">std</span>::<span class="built_in">string</span> str;</span><br><span class="line">    <span class="built_in">std</span>::<span class="built_in">string</span> ext;</span><br><span class="line">&#125;;</span><br><span class="line">Param::Param()&#123;</span><br><span class="line">    param_impl_.reset(<span class="keyword">new</span> ParamImpl);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 析构函数必须要</span></span><br><span class="line">Param::~Param() = <span class="keyword">default</span>;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Param::SetNum</span><span class="params">(<span class="keyword">int</span> num)</span></span>&#123;</span><br><span class="line">    param_impl_-&gt;num = num;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">Param::GetNum</span><span class="params">()</span> <span class="keyword">const</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span>  param_impl_-&gt;num;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Param::SetStr</span><span class="params">(<span class="keyword">const</span> <span class="built_in">std</span>::<span class="built_in">string</span> &amp;str)</span></span>&#123;</span><br><span class="line">    param_impl_-&gt;str = str;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Param::SetExt</span><span class="params">(<span class="keyword">const</span> <span class="built_in">std</span>::<span class="built_in">string</span> &amp;ext)</span></span>&#123;</span><br><span class="line">    param_impl_-&gt;ext = ext;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="built_in">std</span>::<span class="built_in">string</span> <span class="title">Param::GetStr</span><span class="params">()</span> <span class="keyword">const</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> param_impl_-&gt;str;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="built_in">std</span>::<span class="built_in">string</span> <span class="title">Param::GetExt</span><span class="params">()</span> <span class="keyword">const</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> param_impl_-&gt;ext;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">TestFun</span><span class="params">(<span class="keyword">const</span> Param &amp;param)</span></span>&#123;</span><br><span class="line">    <span class="built_in">std</span>::<span class="built_in">cout</span>&lt;&lt;<span class="string">"num:"</span>&lt;&lt;param.GetNum()&lt;&lt;<span class="string">" str:"</span>&lt;&lt;param.GetStr().c_str()&lt;&lt;<span class="string">"ext:"</span>&lt;&lt;param.GetExt().c_str()&lt;&lt;<span class="built_in">std</span>::<span class="built_in">endl</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>通过上面的方式，不会直接暴露成员函数，而是提供接口设置或者获取，而在实现中，即便出现新的版本增加了接口，最多也只是获取到默认值，而不会导致程序崩溃。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>本文和之前的文章实现方法是一样的，这样不暴露成员的做法，更大程度避免了链接库不一致导致的问题，你学会了吗？</p><p>源码地址：<a href="https://github.com/yanbinghu/examples/tree/main/api_impl" target="_blank" rel="noopener">源代码</a></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;之前在《&lt;a href=&quot;http://www.yanbinghu.com/2020/11/06/3161.html&quot;&gt;PIMPL-隐藏接口实现细节&lt;/a&gt;》中介绍了一种隐藏类的私有成员的方法，或者说隐藏接口实现细节的方法-PIMPL。&lt;br&gt;假设提供的接口的入参比较复杂，可能有人会考虑使用结构体作为入参。当你考虑这么做的时候，灾难也将会随之而来……&lt;br&gt;
    
    </summary>
    
      <category term="设计模式" scheme="https://www.yanbinghu.com/categories/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
    
    
      <category term="CPP" scheme="https://www.yanbinghu.com/tags/CPP/"/>
    
      <category term="设计模式" scheme="https://www.yanbinghu.com/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
    
      <category term="api设计" scheme="https://www.yanbinghu.com/tags/api%E8%AE%BE%E8%AE%A1/"/>
    
  </entry>
  
  <entry>
    <title>PIMPL-隐藏接口实现细节</title>
    <link href="https://www.yanbinghu.com/2020/11/06/3161.html"/>
    <id>https://www.yanbinghu.com/2020/11/06/3161.html</id>
    <published>2020-11-06T08:00:00.000Z</published>
    <updated>2020-11-07T08:44:53.926Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>有时候我们需要提供对外的API，通常会以头文件的形式提供。举个简单的例子：<br>提供一个从某个指定数开始打印的接口，<br><a id="more"></a><br>头文件内容如下：<br><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//来源：公众号编程珠玑</span></span><br><span class="line"><span class="comment">//作者：守望先生</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">ifndef</span> _TEST_API_H</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> _TEST_API_H</span></span><br><span class="line"><span class="comment">//test_api.h</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">TestApi</span>&#123;</span></span><br><span class="line">  <span class="keyword">public</span>:</span><br><span class="line">    TestApi(<span class="keyword">int</span> s):start(s)&#123;&#125;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">TestPrint</span><span class="params">(<span class="keyword">int</span> num)</span></span>;</span><br><span class="line">  <span class="keyword">private</span>:</span><br><span class="line">    <span class="keyword">int</span> start_ = <span class="number">0</span>;</span><br><span class="line">&#125;;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span> <span class="comment">//_TEST_API_H</span></span></span><br></pre></td></tr></table></figure></p><p>实现文件如下：<br><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//来源：公众号编程珠玑</span></span><br><span class="line"><span class="comment">//作者：守望先生</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"test_api.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="comment">//test_api.cc</span></span><br><span class="line">TestApi::TestPrint(<span class="keyword">int</span> num)&#123;</span><br><span class="line">  <span class="keyword">for</span>(<span class="keyword">int</span> i = start_; i &lt; num; i++)&#123;</span><br><span class="line">    <span class="built_in">std</span>::<span class="built_in">cout</span>&lt;&lt; i &lt;&lt;<span class="built_in">std</span>::<span class="built_in">endl</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>类TestApi中有一个私有变量start_，头文件中是可以看到的。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"test_api.h"</span></span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span>&#123;</span><br><span class="line">  TestApi test_api&#123;<span class="number">10</span>&#125;;</span><br><span class="line">  test_api.TestPrint(<span class="number">15</span>);</span><br><span class="line">  <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="常规实现缺点"><a href="#常规实现缺点" class="headerlink" title="常规实现缺点"></a>常规实现缺点</h2><p>从前面的内容来看， 一切都还正常，但是有什么问题呢？</p><ul><li>头文件暴露了私有成员</li><li>实现与接口耦合</li><li>编译耦合</li></ul><p>第一点可以很明显的看出来，其中的私有变量start_能否在头文件中看到，如果实现越来越复杂，这里可能也会出现更多的私有变量。有人可能会问，私有变量外部也不能访问，暴露又何妨？</p><p>不过你只是提供几个接口，给别人看到这么多信息干啥呢？这样就会导致实现和接口耦合在了一起。</p><p>另外一方面，如果有另外一个库使用了这个库，而你的这个库实现变了，头文件就会变，而头文件一旦变动，就需要所有使用了这个库的程序都要重新编译！</p><p>这个代价是巨大的。</p><p>所以，我们应该尽可能地保证头文件不变动，或者说，尽可能隐藏实现，隐藏私有变量。</p><h2 id="PIMPL"><a href="#PIMPL" class="headerlink" title="PIMPL"></a>PIMPL</h2><p>Pointer to implementation，由指针指向实现，而不过多暴露细节。废话不多说，上代码：<br><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//来源：公众号编程珠玑</span></span><br><span class="line"><span class="comment">//作者：守望先生</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">ifndef</span> _TEST_API_H</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> _TEST_API_H</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;memory&gt;</span></span></span><br><span class="line"><span class="comment">//test_api.h</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">TestApi</span>&#123;</span></span><br><span class="line">  <span class="keyword">public</span>:</span><br><span class="line">    TestApi(<span class="keyword">int</span> s);</span><br><span class="line">    ~TestApi();</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">TestPrint</span><span class="params">(<span class="keyword">int</span> num)</span></span>;</span><br><span class="line">  <span class="keyword">private</span>:</span><br><span class="line">    <span class="class"><span class="keyword">class</span> <span class="title">TestImpl</span>;</span></span><br><span class="line">    <span class="built_in">std</span>::<span class="built_in">unique_ptr</span>&lt;TestImpl&gt; test_impl_;</span><br><span class="line">&#125;;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span> <span class="comment">//_TEST_API_H</span></span></span><br></pre></td></tr></table></figure></p><p>从这个头文件中，我们可以看到：</p><ul><li>实现都在TestImpl中，因为只有一个私有的TestImpl变量，可以预见到，实现变化时，这个头文件是基本不需要动的</li><li>test<em>impl</em>是一个unique_ptr，因为我们使用的是现代C++，这里需要注意的一点是，它的构造函数和析构函数必须存在，否则会有编译问题。</li></ul><p>我们再来看下具体的实现：<br><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//来源：公众号编程珠玑</span></span><br><span class="line"><span class="comment">//作者：守望先生</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"test_api.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="comment">//test_api.cc</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">TestApi</span>:</span>:TestImpl&#123;</span><br><span class="line">  <span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">TestPrint</span><span class="params">(<span class="keyword">int</span> num)</span></span>;</span><br><span class="line">    TestImpl(<span class="keyword">int</span> s):start_(s)&#123;&#125;</span><br><span class="line">    TestImpl() = <span class="keyword">default</span>;</span><br><span class="line">    ~TestImpl() = <span class="keyword">default</span>;</span><br><span class="line">  <span class="keyword">private</span>:</span><br><span class="line">    <span class="keyword">int</span> start_;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">void</span> TestApi::TestImpl::TestPrint(<span class="keyword">int</span> num)&#123;</span><br><span class="line">  <span class="keyword">for</span>(<span class="keyword">int</span> i = start_; i &lt; num; i++)&#123;</span><br><span class="line">    <span class="built_in">std</span>::<span class="built_in">cout</span>&lt;&lt; i &lt;&lt;<span class="built_in">std</span>::<span class="built_in">endl</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">TestApi::TestApi(<span class="keyword">int</span> s)&#123;</span><br><span class="line">    test_impl_.reset(<span class="keyword">new</span> TestImpl(s));</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">TestApi::TestPrint</span><span class="params">(<span class="keyword">int</span> num)</span></span>&#123;</span><br><span class="line">  test_impl_-&gt;TestPrint(num);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//注意，析构函数需要</span></span><br><span class="line">TestApi::~TestApi() = <span class="keyword">default</span>;</span><br></pre></td></tr></table></figure></p><p>从实现中看到，TestApi中的TestPrint调用了TestImpl中的TestPrint实现，而所有的具体实现细节和私有变量都在TestImpl中，即便实现变更了，其他库不需要重新编译，而仅仅是在生成可执行文件时重新链接。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>从例子中，我们可以看到PIMPL模式中有以下优点：</p><ul><li>降低耦合，头文件稳定，类具体实现改变不影响其他模块的编译，只影响可执行程序的链接</li><li>接口和实现分离，提高稳定性</li></ul><p>当然了，由于实现在另外一个类中，所以会多一次调用，会有性能的损耗，但是这点几乎可以忽略。</p><p>代码地址：<a href="https://github.com/yanbinghu/examples/tree/main/pimpl" target="_blank" rel="noopener">源代码</a></p>]]></content>
    
    <summary type="html">
    
      &lt;h2 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h2&gt;&lt;p&gt;有时候我们需要提供对外的API，通常会以头文件的形式提供。举个简单的例子：&lt;br&gt;提供一个从某个指定数开始打印的接口，&lt;br&gt;
    
    </summary>
    
      <category term="设计模式" scheme="https://www.yanbinghu.com/categories/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
    
    
      <category term="CPP" scheme="https://www.yanbinghu.com/tags/CPP/"/>
    
      <category term="设计模式" scheme="https://www.yanbinghu.com/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
    
      <category term="pimpl" scheme="https://www.yanbinghu.com/tags/pimpl/"/>
    
  </entry>
  
  <entry>
    <title>const修饰的变量无法被外部引用</title>
    <link href="https://www.yanbinghu.com/2020/11/01/37071.html"/>
    <id>https://www.yanbinghu.com/2020/11/01/37071.html</id>
    <published>2020-11-01T09:00:00.000Z</published>
    <updated>2020-11-07T08:24:32.591Z</updated>
    
    <content type="html"><![CDATA[<p> const变量能被其他文件extern引用吗？为什么？</p><p> 先来看一段代码：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 来源：公众号编程珠玑</span></span><br><span class="line"><span class="comment">// main.cc</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span><span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="comment">// 引用外部定义的const_int变量</span></span><br><span class="line"><span class="keyword">extern</span> <span class="keyword">const</span> <span class="keyword">int</span> const_int;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"const_int：%d\n"</span>,const_int);</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// const.cc</span></span><br><span class="line"><span class="comment">// 定义const 变量</span></span><br><span class="line"><span class="keyword">const</span> <span class="keyword">int</span> const_int  = <span class="number">10</span>;</span><br></pre></td></tr></table></figure><p>编译链接：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"> $ g++ -o main main.cc const.cc</span><br><span class="line"> /tmp/ccWHAXxB.o: In function `main&apos;:</span><br><span class="line">main.cc:(.text+0x6): undefined reference to `const_int&apos;</span><br><span class="line">collect2: error: ld returned 1 exit status</span><br></pre></td></tr></table></figure><p>我们发现出现了链接问题，说const_int没有定义的引用，但我们确实在const.cc文件中定义了。</p><p>我们去掉const修饰符再编译一次，发现是可以的。从上面这个编译问题，就引出今天要讲的内容了。至于为什么会编译不过，最后再做解释。</p><p>当然你会发现，按照C代码去编译，是可以编译出来的。后面再解释。<br><a id="more"></a></p><h2 id="链接属性"><a href="#链接属性" class="headerlink" title="链接属性"></a>链接属性</h2><p>我们都知道，C/C++代码的编译通常经过预编译，汇编，编译，链接（参考<a href="https://www.yanbinghu.com/2018/10/10/27133.html">hello程序是怎么生成的</a>）通常会有变量会有三种链接属性：<em>外部链接，内部链接或无链接</em>。</p><p>具有函数作用域，块作用域或者函数原型作用域的变量都是无链接变量；具有文件作用域的变量可以是内部链接也可以是外部链接。而外部链接变量可以在多个文件中使用，内部链接变量只能在一个编译单元中使用（一个源代码文件和它包含的头文件）。</p><p>关于作用域，也可以参考《<a href="https://www.yanbinghu.com/2019/05/08/43402.html">全局变量，静态全局变量，局部变量，静态局部变量</a>》。<br> 说了这么多，举个具体的例子：<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 来源：公众号【编程珠玑】</span></span><br><span class="line"><span class="comment">// 作者：守望先生</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span><span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="keyword">int</span> external_link = <span class="number">10</span>;  <span class="comment">// 文件作用域，外部链接</span></span><br><span class="line"><span class="keyword">static</span> internal_link = <span class="number">20</span>; <span class="comment">// 文件作用域，内部链接</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> no_link = <span class="number">30</span>;   <span class="comment">// 无链接</span></span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"%d %d %d \n"</span>,external_link,internal_link,no_link);</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>这里无链接变量还是比较好区分的，只要不是文件作用域的变量，基本是无链接属性。而文件作用域变量是内部链接还是外部链接呢？只要看前面是否有static修饰即可。当然对于C++，还要看是否有const修饰，后面我们再说。</p><h2 id="如何知道某个变量是什么链接属性？"><a href="#如何知道某个变量是什么链接属性？" class="headerlink" title="如何知道某个变量是什么链接属性？"></a>如何知道某个变量是什么链接属性？</h2><p>举个例子，在前面的代码中，我们按照C代码进行编译：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ gcc -c const.c </span><br><span class="line">$ nm const.o |grep const_int</span><br><span class="line">0000000000000000 R const_int</span><br></pre></td></tr></table></figure></p><p> nm命令在《<a href="https://www.yanbinghu.com/2017/09/10/3456.html">linux常用命令-开发调试篇</a>》中略有介绍，它可以用来查看ELF文件的符号信息。</p><p>从这里的结果可以看到const_int前面是R修饰的，<br>R：该符号位于只读数据区，READONLY的含义</p><p>而<strong>该字母大写，其实也是表示它具有外部链接属性</strong>。</p><p>再看看按照C++代码编译：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ g++ -c const.c</span><br><span class="line">$ nm const.o |grep const_int</span><br><span class="line">0000000000000000 r _ZL9const_int</span><br></pre></td></tr></table></figure></p><p>可以看到，它的修饰符也是r，但是是小写的r，<strong>小写字母表示该变量具有内部链接属性</strong>。</p><p>nm命令非常实用，但本文不是重点。</p><h2 id="const关键字"><a href="#const关键字" class="headerlink" title="const关键字"></a>const关键字</h2><p> 说到const关键字，在《<a href="https://www.yanbinghu.com/2019/01/28/7442.html">const关键字到底该怎么用</a>》和《<a href="https://www.yanbinghu.com/2019/10/31/51504.html">C++中的const与C中的const有何差别？</a>》中已经分析过了，这里简单说一下，被const关键字修饰的变量，表明它是只读的，不希望被修改。</p><h2 id="extern关键字"><a href="#extern关键字" class="headerlink" title="extern关键字"></a>extern关键字</h2><p> extern关键字可以引用外部的定义，想必很多朋友已经很熟悉了，举个例子，如果把最开始的例子中的const关键字去掉，main.cc中的extern的意思，就是说有一个const_int变量，但是它在别的地方定义的，因此这里extern修饰一下，这样在链接阶段，它就会去其他的编译单元中找到它的定义，并链接。</p><p>当然，还有一个不太被关注的作用是，在C++中，它可以改变const变量的链接属性：</p><p>是的，在C++中，它改变了const_int的链接属性。我们可以修改const.c的内容如下：<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">extern</span> <span class="keyword">const</span> <span class="keyword">int</span> const_int  = <span class="number">10</span>;</span><br></pre></td></tr></table></figure></p><p> 然后再查看一下：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"> $ nm const.o |grep const_int</span><br><span class="line">0000000000000000 R const_int</span><br></pre></td></tr></table></figure></p><p>发现没有，它前面的修饰变成大写的R了，所以这个时候，你再编译，就能编译过，而不会报错了，对于C，它本来就是外部链接属性，所以根本不会报错。</p><p>extern还有另外一个用法：<br>《<a href="https://www.yanbinghu.com/2019/08/25/29412.html">C++是如何调用C接口的</a>》？</p><h2 id="解疑"><a href="#解疑" class="headerlink" title="解疑"></a>解疑</h2><p>所以，链接报错的通常问题就是找不到定义，原因无非就是：</p><ul><li>未定义</li><li>在其他地方定义了，但是不具备外部链接属性</li><li>定义了，具备外部链接属性，但是链接顺序有问题</li></ul><p>由于在C++中，被const修饰的变量默认为内部链接属性，因为编译会找不到定义。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>本文从一个编译问题，引出了很多内容，包括：</p><ul><li>作用域 —《<a href="https://www.yanbinghu.com/2019/05/08/43402.html">全局变量，静态全局变量，局部变量，静态局部变量</a>》</li><li>const关键字—《<a href="https://www.yanbinghu.com/2019/01/28/7442.html">const关键字到底该怎么用</a>》</li><li>extern关键字 </li><li>nm查看符号表</li></ul>]]></content>
    
    <summary type="html">
    
      &lt;p&gt; const变量能被其他文件extern引用吗？为什么？&lt;/p&gt;
&lt;p&gt; 先来看一段代码：&lt;/p&gt;
&lt;figure class=&quot;highlight c&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;// 来源：公众号编程珠玑&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;// main.cc&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;#&lt;span class=&quot;meta-keyword&quot;&gt;include&lt;/span&gt;&lt;span class=&quot;meta-string&quot;&gt;&amp;lt;stdio.h&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;// 引用外部定义的const_int变量&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;extern&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;int&lt;/span&gt; const_int;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;function&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;title&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;function&quot;&gt;&lt;/span&gt;&amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;built_in&quot;&gt;printf&lt;/span&gt;(&lt;span class=&quot;string&quot;&gt;&quot;const_int：%d\n&quot;&lt;/span&gt;,const_int);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;number&quot;&gt;0&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;// const.cc&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;// 定义const 变量&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;int&lt;/span&gt; const_int  = &lt;span class=&quot;number&quot;&gt;10&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;编译链接：&lt;/p&gt;
&lt;figure class=&quot;highlight plain&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt; $ g++ -o main main.cc const.cc&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt; /tmp/ccWHAXxB.o: In function `main&amp;apos;:&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;main.cc:(.text+0x6): undefined reference to `const_int&amp;apos;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;collect2: error: ld returned 1 exit status&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;我们发现出现了链接问题，说const_int没有定义的引用，但我们确实在const.cc文件中定义了。&lt;/p&gt;
&lt;p&gt;我们去掉const修饰符再编译一次，发现是可以的。从上面这个编译问题，就引出今天要讲的内容了。至于为什么会编译不过，最后再做解释。&lt;/p&gt;
&lt;p&gt;当然你会发现，按照C代码去编译，是可以编译出来的。后面再解释。&lt;br&gt;
    
    </summary>
    
      <category term="Cpp" scheme="https://www.yanbinghu.com/categories/Cpp/"/>
    
    
      <category term="Cpp" scheme="https://www.yanbinghu.com/tags/Cpp/"/>
    
      <category term="Linux" scheme="https://www.yanbinghu.com/tags/Linux/"/>
    
  </entry>
  
  <entry>
    <title>daemon程序实现注意事项</title>
    <link href="https://www.yanbinghu.com/2020/10/06/45761.html"/>
    <id>https://www.yanbinghu.com/2020/10/06/45761.html</id>
    <published>2020-10-06T14:00:00.000Z</published>
    <updated>2020-10-09T14:23:30.178Z</updated>
    
    <content type="html"><![CDATA[<p>之前在《<a href="https://www.yanbinghu.com/2019/12/06/39731.html">如何让程序真正地后台运行</a>》一文中提到了程序后台运行的写法，但是里面的示例程序在某些场景下是会有问题的，这里先不说什么问题，我们先看看这个磁盘满的问题是怎么产生的，通过这篇文章你将会学习到大量linux命令的实操使用。</p><a id="more"></a><h2 id="找到导致磁盘满的程序"><a href="#找到导致磁盘满的程序" class="headerlink" title="找到导致磁盘满的程序"></a>找到导致磁盘满的程序</h2><p>当发现磁盘占用比较多的时候，可以通过下面的命令，查看各个挂载路径的占用情况：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> df -h</span></span><br><span class="line">udev            3.9G     0  3.9G    0% /dev</span><br><span class="line">tmpfs           784M  2.0M  782M    1% /run</span><br><span class="line">/dev/sda11       19G  6.5G   12G   37% /</span><br><span class="line">tmpfs           3.9G   91M  3.8G    3% /dev/shm</span><br><span class="line">tmpfs           5.0M  4.0K  5.0M    1% /run/lock</span><br><span class="line">tmpfs           3.9G     0  3.9G    0% /sys/fs/cgroup</span><br><span class="line">/dev/sda12      9.4G   37M  8.8G    1% /tmp</span><br><span class="line">/dev/sda14      6.4G  168M  5.9G    3% /boot</span><br><span class="line">/dev/sda10       57G  2.0G   52G    4% /home</span><br><span class="line">/dev/sda1       256M   33M  224M   13% /boot/efi</span><br><span class="line">tmpfs           784M   16K  784M    1% /run/user/121</span><br><span class="line">tmpfs           784M   44K  784M    1% /run/user/1000</span><br></pre></td></tr></table></figure><p>当然我这里并没有哪个挂载路径的磁盘占用率比较高，这里假设home占用比较高，然后可以通过：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">$ cd /home</span><br><span class="line">$ du -h --max-depth=1</span><br><span class="line">1.9G./shouwang</span><br><span class="line">16K./lost+found</span><br><span class="line">1.9G.</span><br></pre></td></tr></table></figure><p>这样可以逐层知道哪些目录有了不该有的大文件。</p><p>当然你也可以使用find直接找出大文件，比如查找当前目录下大于800M的文件：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ find . -type f -size +800M</span><br></pre></td></tr></table></figure></p><p>find的用法可以参考《<a href="https://www.yanbinghu.com/2018/12/15/21083.html">find命令高级用法</a>》。</p><p>如果找到了该文件，并且确认是无用文件，那么就可以删除了。</p><p>但是如果<strong>仍然有程序打开了该文件，那么即便你删除了文件，其占用的磁盘空间也并不会释放</strong>，因为仍然它的”文件引用”不是0，文件并不会被删除。<br>在《<a href="https://www.yanbinghu.com/2020/05/10/50696.html">rm删除文件空间就释放了吗？</a>》一文中，有更加详细的解释。</p><p>所以你需要看一下，是否还有程序打开该文件，举个例子：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ lsof config.json</span><br><span class="line">COMMAND  PID     USER   FD   TYPE DEVICE SIZE/OFF    NODE NAME</span><br><span class="line">less    6750 shouwang    4r   REG   8,10      233 3411160 config.json</span><br></pre></td></tr></table></figure></p><p>从上面的结果，可以看到，是less程序打开了config.json文件，并且它的进程id是6750。</p><p>找到进程之后，根据实际情况决定是否需要停止程序，然后删除大文件。</p><h2 id="找不到大文件？"><a href="#找不到大文件？" class="headerlink" title="找不到大文件？"></a>找不到大文件？</h2><p>现实常常可能不如意，比如虽然可以通过df命令看到某些挂载路径磁盘占用率比较高，但是始终找不到大文件，那么你就要考虑，是不是大文件看似被删除了，但是还有程序打开。要找到这样的文件，其实也很简单，前面已经介绍过了：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">lsof |grep deleted</span><br></pre></td></tr></table></figure></p><p>lsof能看到被打开的文件，而如果文件被删除了（比如使用rm命令），但是仍然有程序打开，则会是deleted状态，举个例子：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ touch test.txt</span><br><span class="line">$ less test.txt</span><br></pre></td></tr></table></figure></p><p>创建一个文件test.txt，并随意输入一些内容，然后使用less命令打，随后在另一个终端，删除该文件：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ rm test.txt</span><br><span class="line">$ lsof |grep test.txt |grep deleted</span><br><span class="line">less      6989              shouwang    4r      REG               8,10       134    3541262 /home/shouwang/workspaces/shell/testdeleted/test.txt (deleted)</span><br></pre></td></tr></table></figure></p><p>可以看到打开该文件的进程id为6989，我们看一下这个程序打开的文件：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">$ ls -al /proc/6989/fd</span><br><span class="line">dr-x------ 2 shouwang shouwang  0 10月  6 10:57 .</span><br><span class="line">dr-xr-xr-x 9 shouwang shouwang  0 10月  6 10:56 ..</span><br><span class="line">lrwx------ 1 shouwang shouwang 64 10月  6 10:57 0 -&gt; /dev/pts/1</span><br><span class="line">lrwx------ 1 shouwang shouwang 64 10月  6 10:57 1 -&gt; /dev/pts/1</span><br><span class="line">lrwx------ 1 shouwang shouwang 64 10月  6 10:57 2 -&gt; /dev/pts/1</span><br><span class="line">lr-x------ 1 shouwang shouwang 64 10月  6 10:57 3 -&gt; /dev/tty</span><br><span class="line">lr-x------ 1 shouwang shouwang 64 10月  6 10:57 4 -&gt; &apos;/home/shouwang/workspaces/shell/testdeleted/test.txt (deleted)&apos;</span><br><span class="line">$ du -h</span><br></pre></td></tr></table></figure></p><p>（关于proc虚拟文件系统，可以参考《<a href="https://www.yanbinghu.com/2018/11/18/43716.html">Linux中不可错过的信息宝库</a>》）。从上面也可以看到，文件描述符4的文件为test.txt，但是deleted状态。</p><p>停止这个进程，你会发现所占用的磁盘空间会被释放。</p><h2 id="不完善的daemon实现"><a href="#不完善的daemon实现" class="headerlink" title="不完善的daemon实现"></a>不完善的daemon实现</h2><p>通常在终端启动一个程序后，文件描述符0，1，2通常对应标准输入，标准输出，标准错误。从前面的例子中也能窥见一二，它打开的是/dev/pts/1，其实就是当前终端。更多信息可以参考《<a href="https://www.yanbinghu.com/2018/10/26/9186.html">如何理解Linux shell中“2&gt;&amp;1”</a>》。</p><p>回到开始的问题，之前例子中daemonize的参考实现如下：<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//来源：公众号【编程珠玑】</span></span><br><span class="line"><span class="comment">//https://www.yanbinghu.com</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span><span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span><span class="meta-string">&lt;sys/resource.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span><span class="meta-string">&lt;fcntl.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span><span class="meta-string">&lt;unistd.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span><span class="meta-string">&lt;stdlib.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span><span class="meta-string">&lt;sys/types.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span><span class="meta-string">&lt;sys/stat.h&gt;</span></span></span><br><span class="line"><span class="comment">/*实现仅供参考，可根据实际情况调整*/</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">daemonize</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="comment">/*清除文件权限掩码*/</span></span><br><span class="line">umask(<span class="number">0</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">/*父进程退出*/</span></span><br><span class="line"><span class="keyword">pid_t</span> pid;</span><br><span class="line"><span class="keyword">if</span>((pid=fork()) &lt; <span class="number">0</span>)</span><br><span class="line">&#123;</span><br><span class="line"><span class="comment">/*for 出错*/</span></span><br><span class="line">perror(<span class="string">"fork error"</span>);</span><br><span class="line"><span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">else</span> <span class="keyword">if</span>(<span class="number">0</span> != pid)<span class="comment">/*父进程*/</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"father exit\n"</span>);</span><br><span class="line">        <span class="built_in">exit</span>(<span class="number">0</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">/*子进程，成为组长进程，并且摆脱终端*/</span></span><br><span class="line">setsid();</span><br><span class="line"></span><br><span class="line"><span class="comment">/*修改工作目录*/</span></span><br><span class="line"><span class="keyword">if</span>(chdir(<span class="string">"/"</span>) &lt; <span class="number">0</span>)</span><br><span class="line">&#123;</span><br><span class="line">perror(<span class="string">"change dir failed"</span>);</span><br><span class="line"><span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">rlimit</span> <span class="title">rl</span>;</span></span><br><span class="line"><span class="comment">/*先获取文件描述符最大值*/</span></span><br><span class="line"><span class="keyword">if</span>(getrlimit(RLIMIT_NOFILE,&amp;rl) &lt; <span class="number">0</span>)</span><br><span class="line">&#123;</span><br><span class="line">perror(<span class="string">"get file decription failed"</span>);</span><br><span class="line"><span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">/*如果无限制，则设置为1024*/</span></span><br><span class="line"><span class="keyword">if</span>(rl.rlim_max == RLIM_INFINITY)</span><br><span class="line">rl.rlim_max = <span class="number">1024</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/*为了使得终端有输出，保留了文件描述符0，1，2;实际上父进程可能没有打开2以上的文件描述符*/</span></span><br><span class="line"><span class="keyword">int</span> i;</span><br><span class="line"><span class="keyword">for</span>(i = <span class="number">3</span>;i &lt; rl.rlim_max;i++)</span><br><span class="line">close(i);</span><br><span class="line"><span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span>(<span class="number">0</span> == daemonize())</span><br><span class="line">&#123;</span><br><span class="line">        <span class="keyword">while</span>(<span class="number">1</span>)</span><br><span class="line">        &#123;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"daemonize ok\n"</span>);</span><br><span class="line">    sleep(<span class="number">2</span>);</span><br><span class="line">        &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">&#123;</span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">"daemonize failed\n"</span>);</span><br><span class="line">sleep(<span class="number">1</span>);</span><br><span class="line">&#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>这里注意到，daemonize函数最后关闭了2以上的文件描述符。</p><p>在其中一个终端运行上面的例子：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">$ gcc -o daemon daemon.c  #编译</span><br><span class="line">$ ./daemon   #运行</span><br><span class="line">$ ls -al /proc/`pidof daemon`/fd  #查看打开的文件</span><br><span class="line">dr-x------ 2 shouwang shouwang  0 10月  6 11:26 .</span><br><span class="line">dr-xr-xr-x 9 shouwang shouwang  0 10月  6 11:26 ..</span><br><span class="line">lrwx------ 1 shouwang shouwang 64 10月  6 11:26 0 -&gt; /dev/pts/4</span><br><span class="line">lrwx------ 1 shouwang shouwang 64 10月  6 11:26 1 -&gt; /dev/pts/4</span><br><span class="line">lrwx------ 1 shouwang shouwang 64 10月  6 11:26 2 -&gt; /dev/pts/4</span><br></pre></td></tr></table></figure></p><p>可以看到0，1，2打开的是程序所在的终端，这时关闭该终端，在另外一个终端执行：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$  ls -al /proc/`pidof daemon`/fd </span><br><span class="line">lrwx------ 1 shouwang shouwang 64 10月  6 11:26 0 -&gt; &apos;/dev/pts/4 (deleted)&apos;</span><br><span class="line">lrwx------ 1 shouwang shouwang 64 10月  6 11:26 1 -&gt; &apos;/dev/pts/4 (deleted)&apos;</span><br><span class="line">lrwx------ 1 shouwang shouwang 64 10月  6 11:26 2 -&gt; &apos;/dev/pts/4 (deleted)&apos;</span><br></pre></td></tr></table></figure></p><p>发现0，1，2都是deleted状态了，因为关闭前面启动程序的终端后，也相当于删除了它标准输入输出和标准错误指向的文件。</p><p>实际上，到这里，都没有任何问题，程序中的printf打印最多无法打印出来而已。</p><p>但是，如果程序不是终端启动的呢？或者说没有终端的环境,比如crontab启动，at命令启动：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ at now &lt;&lt;&lt; “./daemon&quot;</span><br></pre></td></tr></table></figure></p><p>at命令表示当前时间执行daemon程序。<br>再看看它打开的文件：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ ls -l /proc/`pidof daemon`/fd</span><br><span class="line">lr-x------ 1 shouwang shouwang 64 10月  6 11:42 0 -&gt; &apos;/var/spool/cron/atjobs/a00001019765fe (deleted)&apos;</span><br><span class="line">lrwx------ 1 shouwang shouwang 64 10月  6 11:42 1 -&gt; &apos;/var/spool/cron/atspool/a00001019765fe (deleted)&apos;</span><br><span class="line">lrwx------ 1 shouwang shouwang 64 10月  6 11:42 2 -&gt; &apos;/var/spool/cron/atspool/a00001019765fe (deleted)&apos;</span><br></pre></td></tr></table></figure></p><p>看见没有，你会发现它打开了一些奇怪的文件。</p><h2 id="为什么会有这些奇怪的文件？"><a href="#为什么会有这些奇怪的文件？" class="headerlink" title="为什么会有这些奇怪的文件？"></a>为什么会有这些奇怪的文件？</h2><p>很明显，我们自己写的程序中并没有打开这样的文件，但是从文件名可以推断，它看能是cron程序打开的。那么怎么会变成daemon程序打开了呢？</p><p>这要从fork说起，之前在《<a href="https://www.yanbinghu.com/2019/08/11/28423.html">如何创建子进程？</a>》中说到过，fork出来的子进程会继承父进程的文件描述符，我们的daemon实现已经将2以上的描述符关闭了，但是并没有关闭0，1，2，而由于daemon程序自己实际上没有打开任何文件，0，1，2是空着的，实际上就变成了打开的是父进程曾经打开的文件。</p><p>但是由于printf持续向标准输出打印信息，即不断向描述符1打开的文件写入内容，而该文件又是deleted状态，最终可能会导致磁盘空间占用不断增大，但是又找不到实际的大文件。</p><p>为了验证我们的想法，可以看下前面的文件内容到底是什么：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">$ tail -5  /proc/`pidof daemon`/fd/1</span><br><span class="line">daemonize ok</span><br><span class="line">daemonize ok</span><br><span class="line">daemonize ok</span><br><span class="line">daemonize ok</span><br><span class="line">daemonize ok</span><br></pre></td></tr></table></figure></p><p>看到了吗，这既是我们程序的打印！<strong>竟然打印到一个毫无相关的文件中了</strong>。</p><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>从上面的例子可以看到，要想实现一个线上可用的daemon程序，还必须重定向标准输入，标准输出和标准错误，比例：<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* redirect stdin, stdout, and stderr to /dev/null */</span></span><br><span class="line">open(<span class="string">"/dev/null"</span>, O_RDONLY);</span><br><span class="line">open(<span class="string">"/dev/null"</span>, O_RDWR);</span><br><span class="line">open(<span class="string">"/dev/null"</span>, O_RDWR);</span><br></pre></td></tr></table></figure></p><p>如果我们不关心这些输入输出，则重定向到/dev/null，相当于丢弃该内容，关于/dev/null，这里有更多的介绍《<a href="https://www.yanbinghu.com/2019/11/20/49894.html">linux下这些特殊的文件</a>》。</p><p>是否要重定向标准输入输出，完全取决于你的实际应用场景，比如某些情况你可能就是需要将标准输出指向父进程的文件，则可以不需要重定向。当然了，<strong>至于实现，更推荐的做法是调用daemon函数</strong>：<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;unistd.h&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">daemon</span><span class="params">(<span class="keyword">int</span> nochdir, <span class="keyword">int</span> noclose)</span></span>;</span><br></pre></td></tr></table></figure></p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>本文主要涉及以下内容：</p><ul><li>查看各挂载路径空间占用情况 </li><li>查看目录空间占用情况</li><li>如何创建子进程—《<a href="https://www.yanbinghu.com/2019/08/11/28423.html">如何创建子进程？</a>》</li><li>标准输入，输出和标准错误—《<a href="https://www.yanbinghu.com/2018/10/26/9186.html">如何理解Linux shell中”2&gt;&amp;1”</a>》</li><li>查看进程打开文件信息—《<a href="https://www.yanbinghu.com/2019/03/05/61180.html">如何查看linux中文件打开情况</a>》</li><li>查找大文件—《<a href="https://www.yanbinghu.com/2018/12/15/21083.html">find命令高级用法</a>》</li><li>/dev/null特殊文件的用法 —《<a href="https://www.yanbinghu.com/2019/11/20/49894.html">linux下这些特殊的文件</a>》</li><li>查找被删除但仍有进程占用的文件</li><li>编写daemon程序注意事项</li></ul>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;之前在《&lt;a href=&quot;https://www.yanbinghu.com/2019/12/06/39731.html&quot;&gt;如何让程序真正地后台运行&lt;/a&gt;》一文中提到了程序后台运行的写法，但是里面的示例程序在某些场景下是会有问题的，这里先不说什么问题，我们先看看这个磁盘满的问题是怎么产生的，通过这篇文章你将会学习到大量linux命令的实操使用。&lt;/p&gt;
    
    </summary>
    
      <category term="linux" scheme="https://www.yanbinghu.com/categories/linux/"/>
    
    
      <category term="linux" scheme="https://www.yanbinghu.com/tags/linux/"/>
    
  </entry>
  
  <entry>
    <title>性能优化-发现性能问题</title>
    <link href="https://www.yanbinghu.com/2020/08/22/63600.html"/>
    <id>https://www.yanbinghu.com/2020/08/22/63600.html</id>
    <published>2020-08-22T14:00:00.000Z</published>
    <updated>2020-12-20T13:40:25.890Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>为了取得程序的一丁点性能提升而大幅度增加技术的复杂性和晦涩性能，这个买卖做不得，这不仅仅是因为复杂的代码容器滋生bug，也因为他会使日后的阅读和维护工作要更加艰难。《Unix编程艺术》</p></blockquote><h2 id="为什么要性能优化"><a href="#为什么要性能优化" class="headerlink" title="为什么要性能优化"></a>为什么要性能优化</h2><p>也许是想要<strong>支持更高的吞吐量，想要更小的延迟，或者提高资源的利用率</strong>等，这些都是性能优化的目标之一。不过需要提醒的是，<strong>不要过早的进行性能优化。</strong>如果当前并没有任何性能问题，又何必耗费这个精力呢？当前一些有助于提高性能的编码习惯还是可以时刻保持的。</p><a id="more"></a><h2 id="目标"><a href="#目标" class="headerlink" title="目标"></a>目标</h2><p>全面的性能优化不是一件简单的事情。本系列文章不在于介绍性能优化原理或者特定的算法优化。旨在分享一些实践中常用到的技巧，<strong>同时也主要关注CPU方面</strong>。</p><h2 id="如何发现性能瓶颈"><a href="#如何发现性能瓶颈" class="headerlink" title="如何发现性能瓶颈"></a>如何发现性能瓶颈</h2><p>解决性能问题的第一步是发现性能问题。如何快速发现性能问题呢？对于本文来说，如何<strong>发现那些使CPU不停地瞎忙的代码呢？</strong>为什么这里是说让CPU瞎忙的代码？</p><p>举个例子，完成某个事情，你可能只需要一个CPU时间片，但是由于代码不够好，使得仍然需要多个CPU时间片。导致CPU非常忙碌，而无法继续提高它的效率。</p><h2 id="top"><a href="#top" class="headerlink" title="top"></a>top</h2><p>这个命令相信大家都用过，可以实时看到进程的一些状态。它的使用方法有很多文章不厌其烦地对其进行了介绍，本文不打算进行介绍。我们可以通过top命令看到某个进程占用的CPU，但是<strong>CPU占用高并不代表它有性能问题</strong>，也有可能是CPU正在有效地高速运转，并没有占着茅坑不拉屎。</p><h2 id="快速发现"><a href="#快速发现" class="headerlink" title="快速发现"></a>快速发现</h2><p>想必我们都听过八二法则，同样的，<strong>80%的性能问题集中于20%的代码</strong>。因此我们只要找到这20%的部分代码，就可以有效地解决一些性能问题。</p><p><strong>本文使用perf命令，它很强大，支持的参数也非常多，不过没关系，本文也没打算全部介绍。</strong></p><p>系统中可能没有perf命令，ubuntu可以使用如下方法安装：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo apt install linux-tools-common</span><br></pre></td></tr></table></figure></p><h2 id="实例"><a href="#实例" class="headerlink" title="实例"></a>实例</h2><p>直接来看示例吧。例子很简单，只是将字符串的字母转为大写罢了。当然了，很多人可能一眼就看出了哪里有性能问题，不过没关系，这个例子只是为了说明perf的应用。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//来源：公众号【编程珠玑】</span></span><br><span class="line"><span class="comment">//作者：守望先生</span></span><br><span class="line"><span class="comment">//toUpper.c</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span><span class="meta-string">&lt;stdlib.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span><span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span><span class="meta-string">&lt;time.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span><span class="meta-string">&lt;ctype.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span><span class="meta-string">&lt;string.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span><span class="meta-string">&lt;sys/time.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> MAX_LEN  1024*1024</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">printCostTime</span><span class="params">(struct timeval *start,struct timeval *end)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(<span class="literal">NULL</span> == start || <span class="literal">NULL</span> == end)</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">long</span> cost = (end-&gt;tv_sec - start-&gt;tv_sec) * <span class="number">1000</span> + (end-&gt;tv_usec - start-&gt;tv_usec)/<span class="number">1000</span>;</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"cost time: %ld ms\n"</span>,cost);</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    srand(time(<span class="literal">NULL</span>));</span><br><span class="line">    <span class="keyword">int</span> min = <span class="string">'a'</span>;</span><br><span class="line">    <span class="keyword">int</span> max = <span class="string">'z'</span>;</span><br><span class="line">    <span class="keyword">char</span> *str = <span class="built_in">malloc</span>(MAX_LEN);</span><br><span class="line"><span class="comment">//申请失败则退出</span></span><br><span class="line">    <span class="keyword">if</span>(<span class="literal">NULL</span> == str)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">"failed\n"</span>);</span><br><span class="line">        <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">unsigned</span> <span class="keyword">int</span> i = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">while</span>(i &lt; MAX_LEN)<span class="comment">//生成随机数</span></span><br><span class="line">    &#123;</span><br><span class="line">        str[i] = ( rand() % ( max - min ) ) + min;</span><br><span class="line">        i++;</span><br><span class="line">    &#125;</span><br><span class="line">    str[MAX_LEN - <span class="number">1</span>] = <span class="number">0</span>; </span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">timeval</span> <span class="title">start</span>,<span class="title">end</span>;</span></span><br><span class="line">    gettimeofday(&amp;start,<span class="literal">NULL</span>);</span><br><span class="line">    <span class="keyword">for</span>(i = <span class="number">0</span>;i &lt; <span class="built_in">strlen</span>(str) ;i++)</span><br><span class="line">    &#123;</span><br><span class="line">        str[i]  = <span class="built_in">toupper</span>( str[i] );</span><br><span class="line">    &#125;</span><br><span class="line">    gettimeofday(&amp;end,<span class="literal">NULL</span>);</span><br><span class="line">    printCostTime(&amp;start,&amp;end);</span><br><span class="line">    <span class="built_in">free</span>(str);</span><br><span class="line">    str = <span class="literal">NULL</span>;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>编译成可执行程序并运行：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ gcc -o toUpper toUpper.c</span><br><span class="line">$ ./toUpper</span><br></pre></td></tr></table></figure></p><p>这个时候我们用top查看结果发现toUpper程序占用CPU 100%：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ top -p `pidof toUpper`</span><br><span class="line">  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND     </span><br><span class="line">24456 root       20   0    5248   2044    952 R 100.0  0.0   0:07.13 toUpper</span><br></pre></td></tr></table></figure></p><p>打开另外一个终端，执行命令：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">$ perf top -p `pidof toUpper`</span><br><span class="line">Samples: 1K of event &apos;cycles:ppp&apos;, Event count (approx.): 657599945</span><br><span class="line">Overhead  Shared Object  Symbol</span><br><span class="line">  99.13%  libc-2.23.so   [.] strlen</span><br><span class="line">   0.19%  [kernel]       [k] perf_event_task_tick</span><br><span class="line">   0.11%  [kernel]       [k] prepare_exit_to_usermode</span><br><span class="line">   0.10%  libc-2.23.so   [.] toupper</span><br><span class="line">   0.09%  [kernel]       [k] rcu_check_callbacks</span><br><span class="line">   0.09%  [kernel]       [k] reweight_entity</span><br><span class="line">   0.09%  [kernel]       [k] task_tick_fair</span><br><span class="line">   0.09%  [kernel]       [k] native_write_msr</span><br><span class="line">   0.09%  [kernel]       [k] trigger_load_balance</span><br><span class="line">   0.00%  [kernel]       [k] native_apic_mem_write</span><br><span class="line">   0.00%  [kernel]       [k] __perf_event_enable</span><br><span class="line">   0.00%  [kernel]       [k] intel_bts_enable_local</span><br></pre></td></tr></table></figure></p><p>其中pidof命令用于获取指定程序名的进程ID。</p><p>看到结果了吗？可以很清楚地看到，strlen函数占用了整个程序99%的CPU，那这个CPU的占用是否可以优化掉呢？我们现在都清楚，显然是可以的，在对每一个字符串进行大写转换时，都进行了字符串长度的计算，显然是没有必要，可以拿到循环之外的。</p><p>同时我们也关注到，这里面有很多符号可能完全没见过，不知道什么含义了，比例如reweight_entity，不过我们知道它前面有着kernel字样，因此也就明白，这是内核干的事情，仅此而已。</p><p>这里实时查看的方法，当然你也可以保存信息进行查看。<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ perf record -e cycles -p `pidof toUpper` -g -a</span><br></pre></td></tr></table></figure></p><p>执行上面的命令一段时间，用于采集相关性能和符号信息，随后ctrl+c中止。默认当前目录下生成perf.data，不过这里面的数据不易阅读，因此执行：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">$ perf report</span><br><span class="line">+  100.00%     0.00%  toUpper  [unknown]          [k] 0x03ee258d4c544155</span><br><span class="line">+  100.00%     0.00%  toUpper  libc-2.23.so       [.] __libc_start_main</span><br><span class="line">+   99.72%    99.34%  toUpper  libc-2.23.so       [.] strlen</span><br><span class="line">     0.21%     0.02%  toUpper  [kernel.kallsyms]  [k] apic_timer_interrupt</span><br><span class="line">     0.19%     0.00%  toUpper  [kernel.kallsyms]  [k] smp_apic_timer_interrupt</span><br><span class="line">     0.16%     0.00%  toUpper  [kernel.kallsyms]  [k] ret_from_intr</span><br><span class="line">     0.16%     0.00%  toUpper  [kernel.kallsyms]  [k] hrtimer_interrupt</span><br><span class="line">     0.16%     0.00%  toUpper  [kernel.kallsyms]  [k] do_IRQ</span><br><span class="line">     0.15%     0.15%  toUpper  libc-2.23.so       [.] toupper</span><br><span class="line">     0.15%     0.00%  toUpper  [kernel.kallsyms]  [k] handle_irq</span><br><span class="line">     0.15%     0.00%  toUpper  [kernel.kallsyms]  [k] handle_edge_irq</span><br><span class="line">     0.15%     0.00%  toUpper  [kernel.kallsyms]  [k] handle_irq_event</span><br><span class="line">     0.15%     0.00%  toUpper  [kernel.kallsyms]  [k] handle_irq_event_percpu</span><br><span class="line">     0.14%     0.00%  toUpper  [kernel.kallsyms]  [k] __handle_irq_event_percpu</span><br><span class="line">     0.14%     0.01%  toUpper  [kernel.kallsyms]  [k] __hrtimer_run_queues</span><br><span class="line">     0.13%     0.00%  toUpper  [kernel.kallsyms]  [k] _rtl_pci_interrupt</span><br></pre></td></tr></table></figure></p><p>其中-g参数为了保存调用调用链，-a表示保存所有CPU信息。</p><p>因此就可以看到采样信息了，怎么样是不是很明显，其中的+部分还可以展开，看到调用链。<br>例如展开的部分信息如下：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">-  100.00%     0.00%  toUpper  libc-2.23.so       [.] __libc_start_main        </span><br><span class="line">   - __libc_start_main                                                         </span><br><span class="line">        99.72% strlen</span><br></pre></td></tr></table></figure></p><p>当然了，实际上你也可以将结果重定向到另外一个文件，便于查看：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">$ perf report &gt; result</span><br><span class="line">$ more result</span><br><span class="line"># Event count (approx.): 23881569776</span><br><span class="line">#</span><br><span class="line"># Children      Self  Command  Shared Object      Symbol                        </span><br><span class="line">                   </span><br><span class="line"># ........  ........  .......  .................  ..............................</span><br><span class="line">...................</span><br><span class="line">#</span><br><span class="line">   100.00%     0.00%  toUpper  [unknown]          [k] 0x03ee258d4c544155</span><br><span class="line">            |</span><br><span class="line">            ---0x3ee258d4c544155</span><br><span class="line">               __libc_start_main</span><br><span class="line">               |          </span><br><span class="line">                --99.72%--strlen</span><br><span class="line"></span><br><span class="line">   100.00%     0.00%  toUpper  libc-2.23.so       [.] __libc_start_main</span><br><span class="line">            |</span><br><span class="line">            ---__libc_start_main</span><br><span class="line">               |          </span><br><span class="line">                --99.72%--strlen</span><br><span class="line"></span><br><span class="line">    99.72%    99.34%  toUpper  libc-2.23.so       [.] strlen</span><br><span class="line">            |          </span><br><span class="line">             --99.34%--0x3ee258d4c544155</span><br></pre></td></tr></table></figure></p><p>这样看也是非常清晰的。</p><p><strong>不过不要高兴地太早，并不是所有情况都能清晰的看到具体问题在哪里的。</strong></p><p>至于本文例子的性能问题怎么解决，相信你已经很清楚了，只需要把strlen提到循环外即可，这里不再赘述。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>本文的例子过于简单粗暴，但是足够说明perf的使用，快速发现程序中占用CPU较高的部分，至于该部分能否被优化，是否正常就需要进一步分析了。不过别急，后续将会分享一些常见的可优化的性能点。</p>]]></content>
    
    <summary type="html">
    
      &lt;blockquote&gt;
&lt;p&gt;为了取得程序的一丁点性能提升而大幅度增加技术的复杂性和晦涩性能，这个买卖做不得，这不仅仅是因为复杂的代码容器滋生bug，也因为他会使日后的阅读和维护工作要更加艰难。《Unix编程艺术》&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;为什么要性能优化&quot;&gt;&lt;a href=&quot;#为什么要性能优化&quot; class=&quot;headerlink&quot; title=&quot;为什么要性能优化&quot;&gt;&lt;/a&gt;为什么要性能优化&lt;/h2&gt;&lt;p&gt;也许是想要&lt;strong&gt;支持更高的吞吐量，想要更小的延迟，或者提高资源的利用率&lt;/strong&gt;等，这些都是性能优化的目标之一。不过需要提醒的是，&lt;strong&gt;不要过早的进行性能优化。&lt;/strong&gt;如果当前并没有任何性能问题，又何必耗费这个精力呢？当前一些有助于提高性能的编码习惯还是可以时刻保持的。&lt;/p&gt;
    
    </summary>
    
      <category term="性能优化" scheme="https://www.yanbinghu.com/categories/%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/"/>
    
    
      <category term="性能优化" scheme="https://www.yanbinghu.com/tags/%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/"/>
    
  </entry>
  
  <entry>
    <title>makefile入门</title>
    <link href="https://www.yanbinghu.com/2019/12/29/53040.html"/>
    <id>https://www.yanbinghu.com/2019/12/29/53040.html</id>
    <published>2019-12-29T12:00:00.000Z</published>
    <updated>2021-02-17T01:59:04.344Z</updated>
    
    <content type="html"><![CDATA[<p>作为Linux下的C/C++开发者，没接触过makefile一定说不过去，通常构建大型的C/C++项目都离不开makefile，也许你使用的是cmake或者其他类似的工具，但它们的本质都是类似的。</p><p>作为一个轻度使用者，应读者要求，斗胆介绍一下makefile，不过与普通的makfile教程不同的是，本文准备从另外一个角度来介绍。如有不妥之处，欢迎指出。<br><a id="more"></a></p><h2 id="makefie到底是什么"><a href="#makefie到底是什么" class="headerlink" title="makefie到底是什么"></a>makefie到底是什么</h2><p>在Linux下，对于下面这个简单的程序<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//来源：公众号【编程珠玑】</span></span><br><span class="line"><span class="comment">//main.c</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;math.h&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> a = <span class="number">10</span>;</span><br><span class="line">    <span class="keyword">int</span> b = <span class="number">4</span>;</span><br><span class="line">    <span class="keyword">int</span> c = <span class="built_in">pow</span>(a,b);</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"10^4 = %d"</span>,c);</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>我们通常使用gcc就可以编译得到想要的程序了：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ gcc -o main main.c -lm</span><br></pre></td></tr></table></figure></p><p>（如果不理解为什么要加-lm，请参考《<a href="https://www.yanbinghu.com/2018/10/06/46212.html">一个奇怪的链接问题</a>》）。</p><p>对于单个文件的简单程序，一条命令就可以直接搞定了（编译+连接），但是如果是一个复杂的工程，可能有成千上万个文件，然后需要链接大量的动态或静态库。试想一下，你还想一条一条命令执行吗？<strong>懒惰的基因是刻在程序员骨子里的。</strong></p><p>因此你可能会想，那我写个脚本好了。嗯，听起来好多了。</p><p>文件多就多，你告诉我要编译哪里的文件，我遍历一下就好了，你再告诉我要链接哪些库，我一一帮你链接上就好了。</p><p>然而到这里又会想，既然编译链接都是这么类似的过程，能不能给它们写一些通用的规则，搞得这么复杂干嘛？然后按照规则去执行就好了。</p><p>而makefile就是这样的一个规则文件，make是规则的解释执行者。可以类比shell脚本和bash解释程序的关系。</p><p>所以，makefile并不仅仅用于编译链接，只不过它非常适合用于编译链接。</p><h2 id="makefile什么样？"><a href="#makefile什么样？" class="headerlink" title="makefile什么样？"></a>makefile什么样？</h2><p>它最重要的规则语法如下：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">&lt;target&gt; : &lt;prerequisites&gt; </span><br><span class="line">[tab]  &lt;commands&gt;</span><br></pre></td></tr></table></figure></p><p>咋一看，就这么个玩意？但是什么意思？</p><ul><li>target 要生成的目标文件名称</li><li><prerequisites> 要依赖的文件</prerequisites></li><li>[tab] 对，就是tab键，初学者很容易忽略这个问题，请用tab</li><li><commands> 要执行的指令</commands></li></ul><p>关键内容就这些，但是要细讲会有很多内容，本文仅举个简单的例子。假设要将前面的main.c复制名为pow.c的文件。<br>那么我们可以得到：</p><ul><li>target: pow.c 目标名称</li><li>prerequisites：main.c，即得到pow.c需要有main.c</li><li>commands：cp main.c pow.c</li></ul><p>因此我们得到我们的makefile文件内容如下：<br><figure class="highlight makefile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="section">pow.c:main.c</span></span><br><span class="line">cp main.c pow.c</span><br><span class="line"><span class="section">clean:</span></span><br><span class="line">rm pow.c</span><br></pre></td></tr></table></figure></p><p>假设当前目录下没有main.c文件，然后在当前目录下执行：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ make pow.c</span><br><span class="line">make: *** No rule to make target `main.c&apos;, needed by `pow.c&apos;.  Stop.</span><br></pre></td></tr></table></figure></p><p>我们发现会报错，因为你要依赖的文件找不到，而且也没有其他规则能够生成它。</p><p>现在把main.c放在当前目录下后继续执行：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ make </span><br><span class="line">cp main.c pow.c</span><br></pre></td></tr></table></figure></p><p>看见没有，执行完make命令之后，我们的pow.c文件终于有了。</p><p>而执行下面的命令后：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ make clean</span><br><span class="line">rm pow.c</span><br></pre></td></tr></table></figure></p><p>你就会发现pow.c被删除了。</p><p>如果当前目录有clean文件会发生什么？<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ make clean</span><br><span class="line">make: `clean&apos; is up to date.</span><br></pre></td></tr></table></figure></p><p>至于原因，后面会讲到。</p><p>这里注意，如果你的makefile文件的文件名不是makefile，那么就需要指定具体名字，例如假设前面的文件名为test.txt：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ make -f test.txt</span><br></pre></td></tr></table></figure></p><p>以上例子介绍了makefile使用的基本流程，生成目标，清除目标。然而实际上这里面的门道还有很多，例如伪目标，自动推导，隐晦规则，变量定义。本文作为认识性的文章暂时不具体介绍。</p><p>总结来说就是，给规则，按照规则生成目标。</p><h2 id="makefile做了什么？"><a href="#makefile做了什么？" class="headerlink" title="makefile做了什么？"></a>makefile做了什么？</h2><p>网上有很多教程介绍如何编写makefile的，很多也非常不错。不过本文换个角度来说。</p><p>既然我们要学makefile，那么就需要知道构建C/C++项目的时候，它应该做什么？然后再去学习如何编写makefile。</p><p>实际上它主要做的事情也很清楚，那就是编译和链接。这个在《<a href="https://www.yanbinghu.com/2018/10/10/27133.html">helo程序是如何编程可执行文件的</a>》中已经有所介绍，还不了解的朋友可以简单了解一下。那么放到makefile中具体要做什么呢？</p><ul><li>将源代码文件编译成可重定位目标文件.o（参考《<a href="https://www.yanbinghu.com/2019/06/27/47343.html">静态库和动态库的区别</a>》）</li><li>设置编译器选项，例如是否开启优化，传递宏，打开警告等</li><li>链接，将静态库或动态库与目标文件链接</li></ul><p>所以问题就变成了，如何利用makefile的语法规则快速的将成千上万的.c编译成.o，并且正确链接到需要的库。</p><p>而如果用makefile应该怎么写才能得到我们的程序呢？为了帮助说明，我们把前面的编译命令拆分为两条：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ gcc -g -Wall -c main.c -o main.o</span><br><span class="line">$ gcc -o main main.o -lm</span><br></pre></td></tr></table></figure></p><h4 id="设置编译器"><a href="#设置编译器" class="headerlink" title="设置编译器"></a>设置编译器</h4><p>由于我们使用的是gcc编译器（套件），因此可以像下面这样写：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">CC=gcc</span><br></pre></td></tr></table></figure></p><p>为了扩展性考虑，常常将编译器定义为某个变量，后面使用的时候就会方便很多。</p><h4 id="设置编译选项"><a href="#设置编译选项" class="headerlink" title="设置编译选项"></a>设置编译选项</h4><p>比如我们要设置-g选项用来调试，设置-Wall选项来输出更多警告信息。<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">CFLAGS=-g -Wall</span><br></pre></td></tr></table></figure></p><h4 id="设置链接库"><a href="#设置链接库" class="headerlink" title="设置链接库"></a>设置链接库</h4><p>我们这里只用到了libm.so库<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">LIBS=-lm</span><br></pre></td></tr></table></figure></p><h4 id="编译"><a href="#编译" class="headerlink" title="编译"></a>编译</h4><p>我们的目标文件是main.o依赖main.c，该规则应该是这样的：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">OBJ=main.o</span><br><span class="line">$(OBJ):main.c</span><br><span class="line">$(CC) $(CFLAGS) -c main.c -o $(OBJ)</span><br></pre></td></tr></table></figure></p><p>这样就得到了我们的目标文件。</p><h4 id="链接"><a href="#链接" class="headerlink" title="链接"></a>链接</h4><p>接下来就需要将目标文件和库文件链接在一起了。<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">TARGET=main</span><br><span class="line">$(target):main.o</span><br><span class="line">$(CC) $(CFLAGS) -o $(TARGET) $(OBJ) $(LIBS)</span><br></pre></td></tr></table></figure></p><p>而为了使用make clean，即通常用于清除这些中间文件，因此需要加一个伪目标clean：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">.PHONY:clean</span><br><span class="line">clean:</span><br><span class="line">rm $(OBJ) $(TARGET)</span><br></pre></td></tr></table></figure></p><p>伪目标的意思是，它不是一个真正的要生成的目标文件，.PHONY:clean说明了clean是一个伪目标。在这种情况下，即使当前目录下有clean文件，它也仍然会执行后面的指令。</p><p>否则如果当前目录下有clean文件，将不会执行rm动作，而认为目标文件已经是最新的了。</p><h4 id="完整内容"><a href="#完整内容" class="headerlink" title="完整内容"></a>完整内容</h4><figure class="highlight makefile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">CC=gcc</span><br><span class="line">CFLAGS=-g -Wall</span><br><span class="line">LIBS=-lm</span><br><span class="line">OBJ=main.o</span><br><span class="line"><span class="variable">$(OBJ)</span>:main.c</span><br><span class="line"><span class="variable">$(CC)</span> <span class="variable">$(CFLAGS)</span> -c main.c -o <span class="variable">$(OBJ)</span></span><br><span class="line">TARGET=main</span><br><span class="line"><span class="variable">$(TARGET)</span>:main.o</span><br><span class="line"><span class="variable">$(CC)</span> <span class="variable">$(CFLAGS)</span> -o <span class="variable">$(TARGET)</span> <span class="variable">$(OBJ)</span> <span class="variable">$(LIBS)</span></span><br><span class="line"><span class="meta"><span class="meta-keyword">.PHONY</span>:clean</span></span><br><span class="line"><span class="section">clean:</span></span><br><span class="line">rm <span class="variable">$(OBJ)</span> <span class="variable">$(TARGET)</span></span><br></pre></td></tr></table></figure><p>可以看到，makefile文件中有三个目标，分别是main.o，main和clean，其中clean是一个伪目标。</p><p>注意，由于第一个目标是main.o，因此你单单执行make的时候，它只是会生成main.o而已，如果你再执行一次会发现它提示你说main.o已经是最新的了：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ make</span><br><span class="line">gcc -g -Wall -c main.c -o main.o</span><br><span class="line">$ make</span><br><span class="line">make: `main.o&apos; is up to date.</span><br></pre></td></tr></table></figure></p><p>为了得到main，我们执行：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">$ make main</span><br><span class="line">gcc -g -Wall -c main.c -o main.o</span><br><span class="line">gcc -g -Wall -o main main.o -lm</span><br><span class="line">$ ls </span><br><span class="line">main  main.c  main.o  makefile</span><br></pre></td></tr></table></figure></p><p>当然你也可以调整目标顺序。这里的目标文件main依赖的是main.o，它开始会去找main.o，发现这个文件也没有，就会看是不是有规则会生成main.o，欸，你还别说，真有。main.o又依赖main.c，也有，最终按照规则就会先生成main.o，然后生成mian。</p><p>如果要清除这些目标文件，那么可以执行make clean：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ make clean</span><br><span class="line">rm main.o main</span><br><span class="line">$ ls </span><br><span class="line">main.c  makefile</span><br></pre></td></tr></table></figure></p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>本文主要介绍了两部分内容。</p><h4 id="makefile是什么东西"><a href="#makefile是什么东西" class="headerlink" title="makefile是什么东西"></a>makefile是什么东西</h4><p>它是一个规则文件，里面按照某种语法写好了，然后使用make来解释执行，就像shell脚本要用bash解释运行一样。通常会用makefile来构建C/C++项目。</p><h4 id="构建C-C-项目的makefile做了什么"><a href="#构建C-C-项目的makefile做了什么" class="headerlink" title="构建C/C++项目的makefile做了什么"></a>构建C/C++项目的makefile做了什么</h4><p>makefile主要做下面的事情（以C程序为例）</p><ul><li>用变量保存各种设置项，例如编译选项，编译器，宏，包含的头文件等</li><li>把.c编译成.o</li><li>把.o与库进行链接</li><li>清除生成的文件</li><li>安装程序</li></ul><p>其中最关键的事情就是编译链接，即想办法把.c变成.o（可重定位目标文件）;.o+.so（动态库）+.a（静态库）变成可执行文件。</p><p>对于文本提到的例子，看起来实在有些笨拙，一条指令搞定，却要写这么多行的makefile，但是它却指出了通常编写makefile的基本思路。</p><p>对于一个复杂的项目而言，makefile还有很多东西可介绍，例如如何设置变量，如何交叉编译，如何多个目录编译，如何自动推导，如何分支选择等等。这些都是后话了。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;作为Linux下的C/C++开发者，没接触过makefile一定说不过去，通常构建大型的C/C++项目都离不开makefile，也许你使用的是cmake或者其他类似的工具，但它们的本质都是类似的。&lt;/p&gt;
&lt;p&gt;作为一个轻度使用者，应读者要求，斗胆介绍一下makefile，不过与普通的makfile教程不同的是，本文准备从另外一个角度来介绍。如有不妥之处，欢迎指出。&lt;br&gt;
    
    </summary>
    
      <category term="编译链接" scheme="https://www.yanbinghu.com/categories/%E7%BC%96%E8%AF%91%E9%93%BE%E6%8E%A5/"/>
    
    
      <category term="C" scheme="https://www.yanbinghu.com/tags/C/"/>
    
  </entry>
  
  <entry>
    <title>多线程一定更快吗</title>
    <link href="https://www.yanbinghu.com/2019/12/25/46016.html"/>
    <id>https://www.yanbinghu.com/2019/12/25/46016.html</id>
    <published>2019-12-25T14:00:00.000Z</published>
    <updated>2020-10-05T04:01:06.748Z</updated>
    
    <content type="html"><![CDATA[<p>在《<a href="https://www.yanbinghu.com/2019/12/23/52416.html">多线程排序</a>》中介绍了多线程排序，似乎看起来多线程快了很多，然而多线程就一定更快吗？<br><a id="more"></a></p><h2 id="为什么多线程就不一定快？"><a href="#为什么多线程就不一定快？" class="headerlink" title="为什么多线程就不一定快？"></a>为什么多线程就不一定快？</h2><p>还是拿《<a href="https://www.yanbinghu.com/2019/12/23/52416.html">多线程排序</a>》中的程序举例，下面是各个线程数量的排序结果:</p><div class="table-container"><table><thead><tr><th>线程数</th><th>时间/s</th></tr></thead><tbody><tr><td>1</td><td>2.393644</td></tr><tr><td>2</td><td>1.367392</td></tr><tr><td>3</td><td>1.386448</td></tr><tr><td>4</td><td>1.036919</td></tr><tr><td>5</td><td>1.097992</td></tr><tr><td>6</td><td>1.218000</td></tr><tr><td>7</td><td>1.184615</td></tr><tr><td>8</td><td>1.176258</td></tr></tbody></table></div><p>以上结果可能不准确，但是体现了一些变化趋势，即并不是线程数量越多越快，也不是单线程最快，而是线程数为4的时候最快。</p><p>为什么呢？</p><p>原因在于我的机器只有4个逻辑CPU，因此4是最合适的。为了不解释太多术语，简单解释一下。一个CPU就像一条流水线，会执行一系列指令，当你很多指定拆成4份（4线程）的时候，它是正好最合适的，少的时候，有一个闲着；而多了，就会存在抢占的情况。举个简单的例子，假设有4个水管可以出水，你现在去接水，那么你在每个水管下放一个桶去接水，自然要比只在一个水管下去接水要快的，但是如果你的水桶数量多于水管数，为了每个水桶都要有水，你在这个过程中就需要去切换水桶，每个水桶换一下，才能都接得上，<strong>而换的这个过程就像线程的上下文切换带来的开销</strong>。</p><p>因此，<strong>并不是线程越多越快，最合适的才最快。</strong></p><h2 id="单线程有时候反而更快"><a href="#单线程有时候反而更快" class="headerlink" title="单线程有时候反而更快"></a>单线程有时候反而更快</h2><p>说到这你可能更会奇怪了，为什么单线程有时候反而会更快呢？还是拿接水为例，假设虽然有4个水管，但是你只有一个桶，因此你一个人从这个水管里一直接水是最快的，而如果你拿两个桶，这个接一点，又换一下，那个接一点，又换一下，中间显然有中断，相同时间内单个桶接的比较多；这就是单核CPU妄图使用多线程提高效率或者每个线程都需要竞争同一把锁而实际可能会导致更慢的缘故。</p><p>举个绑核的例子：<br><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> taskset -c 1 taskset -c 1 ./multiThread 4</span></span><br><span class="line">thread num:4</span><br><span class="line">time 2.378558</span><br></pre></td></tr></table></figure></p><p>我使用taskset将程序绑定在一个CPU上运行，可以看其时间足足是不绑核的时候的两倍有余。</p><p>什么叫都需要竞争呢？举个极端的例子，我们修改前面的工作线程代码如下：<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/*比较线程，采用快速排序*/</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> * <span class="title">workThread</span><span class="params">(<span class="keyword">void</span> *arg)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    pthread_mutex_lock(&amp;mutex);</span><br><span class="line">    SortInfo *sortInfo = (SortInfo*)arg;</span><br><span class="line">    <span class="keyword">long</span> idx = sortInfo-&gt;startIdx;</span><br><span class="line">    <span class="keyword">long</span> num = sortInfo-&gt;num;</span><br><span class="line">    qsort(&amp;nums[idx],num,<span class="keyword">sizeof</span>(<span class="keyword">long</span>),compare);</span><br><span class="line">    pthread_mutex_unlock(&amp;mutex);</span><br><span class="line">    pthread_barrier_wait(&amp;b);</span><br><span class="line">    <span class="keyword">return</span> ((<span class="keyword">void</span>*)<span class="number">0</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>这里的例子比较极端，在排序的时候都给它们加上了锁（关于锁，后面会有文章进行更加详细的介绍。），即哪个线程拿到了锁，就可以继续工作，没有拿到的继续等待。使用完成后再释放。<br>在这样的情况下，看看4线程还有效果吗？<br><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> ./multiThread 4</span></span><br><span class="line">thread num:4</span><br><span class="line">time 2.480588</span><br></pre></td></tr></table></figure></p><p>是最快的时候两倍多的时间！而且还比单个线程的时候要慢！！！</p><p>而另外一种情况，比如说从队列中取出数据，然后进行耗时处理，那么对取出数据的操作进行加锁是可行的，多线程的情况仍然能提高处理速度。但如果你仅仅是读取数据，那么单线程的情况可能会比多线程要快，<strong>因为它避免了线程上下文切换的开销</strong>。</p><h4 id="扩展介绍-绑核"><a href="#扩展介绍-绑核" class="headerlink" title="扩展介绍-绑核"></a>扩展介绍-绑核</h4><p>为什么要绑核？</p><ul><li>充分利用CPU，减少CPU之间上下文切换</li><li>指定程序运行在指定CPU，便于区分</li></ul><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> taskset -c 1 ./proName</span></span><br></pre></td></tr></table></figure><p>将proName绑定在第二个核。<br><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> taskset -c 1-3  ./proName</span></span><br></pre></td></tr></table></figure></p><p>绑定运行在第二个到第四个核。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> taskset -p 3569</span></span><br><span class="line">pid 3569's current affinity mask: f</span><br></pre></td></tr></table></figure><p>查看进程3569当前运行在哪个核上。</p><p>mask f转为二进制即为1111，因此四个核都有运行。</p><p>当然除了命令行，还有函数接口可以使用，这里就不再扩展了。</p><h2 id="如何查看机器的CPU数量"><a href="#如何查看机器的CPU数量" class="headerlink" title="如何查看机器的CPU数量"></a>如何查看机器的CPU数量</h2><p>物理CPU个数，就是你实际CPU的个数：<br><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> cat /proc/cpuinfo | grep <span class="string">"physical id"</span> | sort -u | wc -l </span></span><br><span class="line">1</span><br></pre></td></tr></table></figure></p><p>CPU物理核数，就是你的一个CPU上有多少个核心，现在很多CPU都是多核：<br><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> cat /proc/cpuinfo | grep <span class="string">"core id"</span> | sort -u | wc -l</span></span><br><span class="line">2</span><br></pre></td></tr></table></figure></p><p> CPU逻辑核数，一颗物理CPU可以有多个物理内核，加上超线程技术，会让CPU看起来有很多个：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ cat /proc/cpuinfo | grep &quot;processor&quot; | sort -u | wc -l</span><br><span class="line">4</span><br></pre></td></tr></table></figure></p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p><strong>线程上下文切换是有开销的，如果它的收益不能超过它的开销，那么使用多线程来提高效率将得不偿失</strong>。因此不要盲目推崇多线程。如果为了提高效率采用多线程，那么线程中最多应为逻辑CPU数。也就是说如果你的程序绑在一个核上或者你只有一个CPU一个核，那么采用多线程只能提高同时处理的能力，而不能提高处理效率。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;在《&lt;a href=&quot;https://www.yanbinghu.com/2019/12/23/52416.html&quot;&gt;多线程排序&lt;/a&gt;》中介绍了多线程排序，似乎看起来多线程快了很多，然而多线程就一定更快吗？&lt;br&gt;
    
    </summary>
    
      <category term="C" scheme="https://www.yanbinghu.com/categories/C/"/>
    
    
      <category term="C" scheme="https://www.yanbinghu.com/tags/C/"/>
    
  </entry>
  
  <entry>
    <title>多线程排序</title>
    <link href="https://www.yanbinghu.com/2019/12/23/52416.html"/>
    <id>https://www.yanbinghu.com/2019/12/23/52416.html</id>
    <published>2019-12-23T14:00:00.000Z</published>
    <updated>2020-10-05T04:01:06.748Z</updated>
    
    <content type="html"><![CDATA[<p>在《<a href="https://www.yanbinghu.com/2019/12/17/19410.html">系统编程-多线程</a>》中已经了解了多线程的一些特点，其中包括快！那么今天就来看看如何利用多线程来排序。<br><a id="more"></a></p><h2 id="思路"><a href="#思路" class="headerlink" title="思路"></a>思路</h2><p>我们的思路是这样的：</p><ul><li>假设有N个线程，则将数组数M据分为N组</li><li>每个线程对其中的一组数据使用库函数提供的快速排序算法</li><li>所有线程排序完成后，将每组排序好的数组合并</li></ul><p>举个例子，使用4个线程对11个数据进行排序：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">12,10,4,7,9,6,8,1,5,16,11</span><br></pre></td></tr></table></figure></p><p>由于4不能被10整除，因此，前面三个线程，每个负责排序10%（4-1）= 3三个数，最后一个线程负责排序最后两个数。</p><div class="table-container"><table><thead><tr><th>线程0</th><th>线程1</th><th>线程2</th><th>线程3</th></tr></thead><tbody><tr><td>12,10,4</td><td>7,9,6</td><td>8,1,5</td><td>16,11</td></tr></tbody></table></div><p>假设这4个线程都完成了自己的工作后，内容如下：</p><div class="table-container"><table><thead><tr><th>线程0</th><th>线程1</th><th>线程2</th><th>线程3</th></tr></thead><tbody><tr><td>4,10,12</td><td>6,7,9</td><td>1,5,8</td><td>11,16</td></tr></tbody></table></div><p>最后由主线程将已经排好的每组进行合并：</p><ul><li>比较每组的第一个，选出最小的一个，这里是线程2的1，放到新数组的下标0处</li><li>将1放到新的数组最开始的位置，线程的下次计较的内容后移，即下次比较时，比较线程2的第二个数。</li><li>循环比较</li></ul><p>最终可以得到合并的数据：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">1 4 5 6 7 8 9 10 11 12 16</span><br></pre></td></tr></table></figure></p><h2 id="屏障"><a href="#屏障" class="headerlink" title="屏障"></a>屏障</h2><p>通过上面的分析，我们需要多个线程进行排序后，一起交给主线程合并，因此需要有方法等待所有线程完成事情之后，再退出。<br>在《<a href="https://www.yanbinghu.com/2019/12/17/19410.html">系统编程-多线程</a>》中介绍了pthread_join，今天我们使用pthread_barrier_wait。<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;pthread.h&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">pthread_barrier_destroy</span><span class="params">(<span class="keyword">pthread_barrier_t</span> *barrier)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">pthread_barrier_init</span><span class="params">(<span class="keyword">pthread_barrier_t</span> *<span class="keyword">restrict</span> barrier,</span></span></span><br><span class="line">const pthread_barrierattr_t *restrict attr, unsigned count)；</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">pthread_barrier_wait</span><span class="params">(<span class="keyword">pthread_barrier_t</span> *barrier)</span></span>;</span><br></pre></td></tr></table></figure></p><p>在解释之前说明一下基本原理，pthread_barrier_wait等待某一个条件达到（计数到达），一旦达到后就会继续往后执行。当然了，如果你希望各个线程完成它自己的工作，主线程再进行合并动作，则你等待的数量可以再加一个。：<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//来源：公众号【编程珠玑】</span></span><br><span class="line"><span class="comment">//https://www.yanbinghu.com</span></span><br><span class="line"><span class="comment">//barrier.c</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;pthread.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;unistd.h&gt;</span></span></span><br><span class="line"><span class="keyword">pthread_barrier_t</span> b;</span><br><span class="line"><span class="function"><span class="keyword">void</span> *<span class="title">workThread</span><span class="params">(<span class="keyword">void</span> * arg)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"thread %d\n"</span>,*(<span class="keyword">int</span>*)arg);</span><br><span class="line">    pthread_barrier_wait(&amp;b);</span><br><span class="line">    <span class="keyword">return</span> (<span class="keyword">void</span>*)<span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> threadNum = <span class="number">4</span>;</span><br><span class="line">    <span class="keyword">int</span> err;</span><br><span class="line">    <span class="comment">/*计数为创建线程数+1*/</span></span><br><span class="line">    pthread_barrier_init(&amp;b,<span class="literal">NULL</span>,threadNum + <span class="number">1</span>);</span><br><span class="line">    <span class="keyword">int</span> i = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">pthread_t</span> tid;</span><br><span class="line">    <span class="comment">/*创建多个线程*/</span></span><br><span class="line">    <span class="keyword">for</span>(i = <span class="number">0</span>;i &lt; threadNum; i++)</span><br><span class="line">    &#123;</span><br><span class="line">        err = pthread_create(&amp;tid,<span class="literal">NULL</span>,workThread,(<span class="keyword">void</span>*)&amp;i);</span><br><span class="line">        <span class="keyword">if</span>(<span class="number">0</span> != err)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="built_in">printf</span>(<span class="string">"create thread failed\n"</span>);</span><br><span class="line">            <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">"tid:%ld\n"</span>,tid);</span><br><span class="line">        usleep(<span class="number">10000</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    pthread_barrier_wait(&amp;b);</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"all thread finished\n"</span>);</span><br><span class="line">    <span class="comment">/*销毁*/</span></span><br><span class="line">    pthread_barrier_destroy(&amp;b);</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>其中，pthread_barrier_init用来初始化相关资源，而pthread_barrier_destroy用来销毁相关资源。<br>编译运行：<br><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> gcc -o barrier barrier.c  -lpthread</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> ./barrier</span></span><br><span class="line">tid:140323085256448</span><br><span class="line">thread 0</span><br><span class="line">tid:140323076863744</span><br><span class="line">thread 1</span><br><span class="line">tid:140323068471040</span><br><span class="line">thread 2</span><br><span class="line">tid:140323060078336</span><br><span class="line">thread 3</span><br><span class="line">all thread finished</span><br></pre></td></tr></table></figure></p><h2 id="比较函数"><a href="#比较函数" class="headerlink" title="比较函数"></a>比较函数</h2><p>为了使用qsort函数，我们需要实现自己的比较函数，参考《<a href="https://www.yanbinghu.com/2019/01/03/3593.html">高级指针话题-函数指针</a>》：<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//来源：公众号【编程珠玑】</span></span><br><span class="line"><span class="comment">//https:www.yanbinghu.com</span></span><br><span class="line"><span class="comment">/*比较函数*/</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">compare</span><span class="params">(<span class="keyword">const</span> <span class="keyword">void</span>* num1, <span class="keyword">const</span> <span class="keyword">void</span>* num2)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">long</span> l1 = *(<span class="keyword">long</span>*)num1;</span><br><span class="line">    <span class="keyword">long</span> l2 = *(<span class="keyword">long</span>*)num2;</span><br><span class="line">    <span class="keyword">if</span>(l1 == l2)</span><br><span class="line">        <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span>(l1 &lt; l2)</span><br><span class="line">        <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><h2 id="合并"><a href="#合并" class="headerlink" title="合并"></a>合并</h2><p>对于每个线程完成它自己的任务之后，需要合并所有内容，关于合并的逻辑前面已经举例了，这里不再多介绍。<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//来源：公众号【编程珠玑】</span></span><br><span class="line"><span class="comment">//https://www.yanbinghu.com</span></span><br><span class="line"><span class="comment">/*要排序的数组信息*/</span></span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">SortInfo_t</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line">    <span class="keyword">long</span> startIdx; <span class="comment">//数组启始下标</span></span><br><span class="line">    <span class="keyword">long</span> num;<span class="comment">//要排序的数量</span></span><br><span class="line">&#125;SortInfo;</span><br><span class="line"><span class="comment">/*合并线程已经排序好的内容*/</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">merge</span><span class="params">(SortInfo *sortInfos,<span class="keyword">size_t</span> threadNum)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">long</span> idx[threadNum];</span><br><span class="line">    <span class="built_in">memset</span>(idx,<span class="number">0</span>,threadNum);</span><br><span class="line">    <span class="keyword">long</span> i,minidx,sidx,num;</span><br><span class="line">    <span class="keyword">for</span>(i = <span class="number">0</span>;i &lt; threadNum;i++)</span><br><span class="line">    &#123;</span><br><span class="line">        idx[i] = sortInfos[i].startIdx;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">for</span>(sidx = <span class="number">0</span>;sidx &lt; NUM;sidx++)</span><br><span class="line">    &#123;</span><br><span class="line">        num = LONG_MAX;</span><br><span class="line">        <span class="keyword">for</span>(i = <span class="number">0</span>;i &lt; threadNum;i++)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">if</span>(idx[i] &lt; (sortInfos[i].startIdx + sortInfos[i].num) &amp;&amp; (nums[idx[i]] &lt; num))</span><br><span class="line">            &#123;</span><br><span class="line">                num = nums[idx[i]];</span><br><span class="line">                minidx = i;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        snums[sidx] = nums[idx[minidx]];</span><br><span class="line">        idx[minidx]++;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><h2 id="随机数生成"><a href="#随机数生成" class="headerlink" title="随机数生成"></a>随机数生成</h2><p>关于生成方法，参考《<a href="https://www.yanbinghu.com/2019/12/22/6131.html">随机数生成的N种姿势</a>》。</p><h2 id="完整代码"><a href="#完整代码" class="headerlink" title="完整代码"></a>完整代码</h2><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/*来源：公众号【编程珠玑】</span></span><br><span class="line"><span class="comment">https://www.yanbinghu.com</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;pthread.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;limits.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/time.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdlib.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;string.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> NUM 8000000L</span></span><br><span class="line"><span class="keyword">long</span> nums[NUM];</span><br><span class="line"><span class="keyword">long</span> snums[NUM];</span><br><span class="line"><span class="keyword">pthread_barrier_t</span> b;</span><br><span class="line"><span class="keyword">pthread_mutex_t</span> mutex;</span><br><span class="line"><span class="comment">/*比较函数*/</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">compare</span><span class="params">(<span class="keyword">const</span> <span class="keyword">void</span>* num1, <span class="keyword">const</span> <span class="keyword">void</span>* num2)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">long</span> l1 = *(<span class="keyword">long</span>*)num1;</span><br><span class="line">    <span class="keyword">long</span> l2 = *(<span class="keyword">long</span>*)num2;</span><br><span class="line">    <span class="keyword">if</span>(l1 == l2)</span><br><span class="line">        <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span>(l1 &lt; l2)</span><br><span class="line">        <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">/*要排序的数组信息*/</span></span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">SortInfo_t</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line">    <span class="keyword">long</span> startIdx; <span class="comment">//数组启始下标</span></span><br><span class="line">    <span class="keyword">long</span> num;<span class="comment">//要排序的数量</span></span><br><span class="line">&#125;SortInfo;</span><br><span class="line"><span class="comment">/*比较线程，采用快速排序*/</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> * <span class="title">workThread</span><span class="params">(<span class="keyword">void</span> *arg)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    pthread_mutex_lock(&amp;mutex);</span><br><span class="line">    SortInfo *sortInfo = (SortInfo*)arg;</span><br><span class="line">    <span class="keyword">long</span> idx = sortInfo-&gt;startIdx;</span><br><span class="line">    <span class="keyword">long</span> num = sortInfo-&gt;num;</span><br><span class="line">    qsort(&amp;nums[idx],num,<span class="keyword">sizeof</span>(<span class="keyword">long</span>),compare);</span><br><span class="line">    pthread_mutex_unlock(&amp;mutex);</span><br><span class="line">    pthread_barrier_wait(&amp;b);</span><br><span class="line">    <span class="keyword">return</span> ((<span class="keyword">void</span>*)<span class="number">0</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">/*合并线程已经排序好的内容*/</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">merge</span><span class="params">(SortInfo *sortInfos,<span class="keyword">size_t</span> threadNum)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">long</span> idx[threadNum];</span><br><span class="line">    <span class="built_in">memset</span>(idx,<span class="number">0</span>,threadNum);</span><br><span class="line">    <span class="keyword">long</span> i,minidx,sidx,num;</span><br><span class="line">    <span class="comment">/*记录索引信息*/</span></span><br><span class="line">    <span class="keyword">for</span>(i = <span class="number">0</span>;i &lt; threadNum;i++)</span><br><span class="line">    &#123;</span><br><span class="line">        idx[i] = sortInfos[i].startIdx;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">/*遍历各个数据，已经比较过后，比较下标增加*/</span></span><br><span class="line">    <span class="keyword">for</span>(sidx = <span class="number">0</span>;sidx &lt; NUM;sidx++)</span><br><span class="line">    &#123;</span><br><span class="line">        num = LONG_MAX;</span><br><span class="line">        <span class="keyword">for</span>(i = <span class="number">0</span>;i &lt; threadNum;i++)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">if</span>(idx[i] &lt; (sortInfos[i].startIdx + sortInfos[i].num) &amp;&amp; (nums[idx[i]] &lt; num))</span><br><span class="line">            &#123;</span><br><span class="line">                num = nums[idx[i]];</span><br><span class="line">                minidx = i;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        snums[sidx] = nums[idx[minidx]];</span><br><span class="line">        idx[minidx]++;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">int</span> argc,<span class="keyword">char</span> *argv[])</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">unsigned</span> <span class="keyword">long</span> i;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/*记录耗费时间*/</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">timeval</span> <span class="title">start</span>,<span class="title">end</span>;</span></span><br><span class="line">    <span class="keyword">long</span> <span class="keyword">long</span> startusec,endusec;</span><br><span class="line">    <span class="keyword">double</span> elapsed;</span><br><span class="line"></span><br><span class="line">  </span><br><span class="line">    <span class="keyword">int</span> err;</span><br><span class="line">    <span class="keyword">pthread_t</span> tid;<span class="comment">/*线程id*/</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">long</span> perThreadNum;</span><br><span class="line">    <span class="keyword">long</span> lastThreadNum;</span><br><span class="line">    </span><br><span class="line">    </span><br><span class="line">    <span class="comment">/*获取线程数量，默认为1*/</span></span><br><span class="line">    <span class="keyword">size_t</span> threadNum = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">if</span>(argc &gt; <span class="number">1</span>)</span><br><span class="line">        threadNum = atoi(argv[<span class="number">1</span>]);</span><br><span class="line">    <span class="keyword">if</span>(<span class="number">0</span> == threadNum)</span><br><span class="line">        threadNum = <span class="number">1</span>;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"thread num:%zu\n"</span>,threadNum);</span><br><span class="line">    SortInfo *sortInfos = <span class="built_in">malloc</span>(<span class="keyword">sizeof</span>(SortInfo)*threadNum);</span><br><span class="line">    <span class="built_in">memset</span>(sortInfos,<span class="number">0</span>,<span class="keyword">sizeof</span>(SortInfo)*threadNum);</span><br><span class="line"></span><br><span class="line">    <span class="comment">/*生成随机数组*/</span></span><br><span class="line">    srandom(time(<span class="literal">NULL</span>));</span><br><span class="line">    <span class="keyword">for</span>(i = <span class="number">0</span>;i &lt; NUM;i++)</span><br><span class="line">    &#123;</span><br><span class="line">        nums[i] = random();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/*如果不能整除，调整最后一个线程处理的数据量*/</span></span><br><span class="line">    <span class="keyword">long</span> PER_THREAD_NUM = NUM / threadNum;</span><br><span class="line">    <span class="keyword">if</span>(<span class="number">0</span> != NUM % threadNum)</span><br><span class="line">    &#123;</span><br><span class="line">        perThreadNum = NUM / (threadNum - <span class="number">1</span>);</span><br><span class="line">        lastThreadNum = NUM % (threadNum - <span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">    &#123;</span><br><span class="line">        perThreadNum = PER_THREAD_NUM;</span><br><span class="line">        lastThreadNum = PER_THREAD_NUM; </span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    gettimeofday(&amp;start,<span class="literal">NULL</span>);</span><br><span class="line">    pthread_barrier_init(&amp;b,<span class="literal">NULL</span>,threadNum + <span class="number">1</span>);</span><br><span class="line">    <span class="comment">/*创建线程，并进行排序，传入要排序的部分*/</span></span><br><span class="line">    <span class="keyword">for</span>(i = <span class="number">0</span>; i &lt; threadNum;i++)</span><br><span class="line">    &#123;</span><br><span class="line">        sortInfos[i].startIdx = i*perThreadNum;</span><br><span class="line">        sortInfos[i].num = perThreadNum;</span><br><span class="line">        <span class="keyword">if</span>(i == threadNum - <span class="number">1</span>)</span><br><span class="line">           sortInfos[i].num = lastThreadNum; </span><br><span class="line">        err = pthread_create(&amp;tid,<span class="literal">NULL</span>,workThread,(<span class="keyword">void</span>*)(&amp;sortInfos[i]));</span><br><span class="line">        <span class="keyword">if</span>(<span class="number">0</span> != err)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="built_in">printf</span>(<span class="string">"create failed\n"</span>);</span><br><span class="line">            <span class="built_in">free</span>(sortInfos);</span><br><span class="line">            <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    pthread_barrier_wait(&amp;b);</span><br><span class="line">    pthread_barrier_destroy(&amp;b);</span><br><span class="line">    <span class="comment">/*合并*/</span></span><br><span class="line">    merge(&amp;sortInfos[<span class="number">0</span>],threadNum);</span><br><span class="line">    gettimeofday(&amp;end,<span class="literal">NULL</span>);</span><br><span class="line">    startusec = start.tv_sec * <span class="number">1000000</span> + start.tv_usec;</span><br><span class="line">    endusec = end.tv_sec * <span class="number">1000000</span> + end.tv_usec;</span><br><span class="line">    elapsed = (<span class="keyword">double</span>)(endusec - startusec)/<span class="number">1000000.0</span>;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"time %f\n"</span>,elapsed);</span><br><span class="line">    <span class="keyword">for</span>(i = <span class="number">0</span>; i &lt; NUM;i++)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="comment">//printf("%ld\n",snums[i]);</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="built_in">free</span>(sortInfos);</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>或阅读原文查看。</p><h2 id="运行结果"><a href="#运行结果" class="headerlink" title="运行结果"></a>运行结果</h2><p>对800W数据进行排序，排序时间：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ threadSort 1</span><br><span class="line">thread num:1</span><br><span class="line">time 2.369488</span><br></pre></td></tr></table></figure></p><p>使用4个线程时：<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ threadSort <span class="number">4</span></span><br><span class="line">thread num:<span class="number">4</span></span><br><span class="line">time <span class="number">1.029097</span></span><br></pre></td></tr></table></figure></p><p>可以看到速度提升是比较明显的。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>可以看到使用4线程排序要比单个线程排序快很多，不过以上实现仅供参考，本文例子可能也存在不妥之处，请根据实际数据情况选择合适的排序算法。但是，多线程就一定快吗？敬请关注下一篇。</p><p>参考：《unix环境高级编程》</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;在《&lt;a href=&quot;https://www.yanbinghu.com/2019/12/17/19410.html&quot;&gt;系统编程-多线程&lt;/a&gt;》中已经了解了多线程的一些特点，其中包括快！那么今天就来看看如何利用多线程来排序。&lt;br&gt;
    
    </summary>
    
      <category term="C" scheme="https://www.yanbinghu.com/categories/C/"/>
    
    
      <category term="C" scheme="https://www.yanbinghu.com/tags/C/"/>
    
  </entry>
  
  <entry>
    <title>C语言生成随机数</title>
    <link href="https://www.yanbinghu.com/2019/12/22/6131.html"/>
    <id>https://www.yanbinghu.com/2019/12/22/6131.html</id>
    <published>2019-12-22T14:10:00.000Z</published>
    <updated>2020-10-05T04:01:06.752Z</updated>
    
    <content type="html"><![CDATA[<p>首先需要说明的是，计算机中生成的随机数严格来说都是伪随机，即非真正的随机数，真正随机数的随机样本不可重现。那么我们来看看代码中有哪些方式可以生成随机数。<br><a id="more"></a></p><h2 id="rand"><a href="#rand" class="headerlink" title="rand"></a>rand</h2><p>rand函数声明如下：<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdlib.h&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">rand</span><span class="params">(<span class="keyword">void</span>)</span></span>;</span><br></pre></td></tr></table></figure></p><p>rand函数返回[0,RAND_MAX）范围的随机整数，在我的机器上，RAND_MAX为2147483647。<br>使用示例：<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/*来源：公众号【编程珠玑】</span></span><br><span class="line"><span class="comment">rand.c</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span><span class="meta-string">&lt;stdlib.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span><span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> i = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">while</span>(i &lt; <span class="number">5</span>)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">"%d "</span>,rand());</span><br><span class="line">        i++;</span><br><span class="line">     &#125;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"\n"</span>);</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>编译运行：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ gcc -o rand rand.c</span><br><span class="line">./rand</span><br><span class="line">1804289383 846930886 1681692777 1714636915 1957747793</span><br></pre></td></tr></table></figure></p><p>多运行几次，你就会惊喜地发现，每次运行的结果都是一样的！！！这还玩个毛线？</p><h2 id="srand"><a href="#srand" class="headerlink" title="srand"></a>srand</h2><p>别急，rand虽然每次运行的结果都是一样的，那是因为它的种子默认为1。每一个种子会有一串看似随机的序列，每次取下一个出来，整体都近乎是随机分布的，但是如果你的种子每次都是一样的，那么每次运行可能得到的结果也是一样的。我们需要利用srand给它一个种子。<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdlib.h&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">srand</span><span class="params">(<span class="keyword">unsigned</span> <span class="keyword">int</span> seed)</span></span>;</span><br></pre></td></tr></table></figure></p><p>为了保证我们每次的得到的随机数不一样，我们必须在每次调用时，都确保种子不一样，因此通常会选择使用时间作为种子。注意这只是通常的种子选择，你可以根据实际使用需求进行选择。</p><p>于是我们在使用之前设置好种子，使用示例：<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/*来源：公众号【编程珠玑】</span></span><br><span class="line"><span class="comment">rand.c</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span><span class="meta-string">&lt;stdlib.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span><span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span><span class="meta-string">&lt;time.h&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    srand(time(<span class="literal">NULL</span>));<span class="comment">//设置随机种子，注意只需要设置一次即可</span></span><br><span class="line">    <span class="keyword">int</span> i = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">while</span>(i &lt; <span class="number">5</span>)<span class="comment">//生成5个随机数</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">"%d "</span>,rand());</span><br><span class="line">        i++;</span><br><span class="line">     &#125;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"\n"</span>);</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>现在好了，每次运行生成的都不一样了。但是还有一个问题，如果这种方式在多线程下使用，也是不可取的，因为rand不是可重入函数。它的每次调用都会修改一些隐藏的属性，因此在多线程中并不会使用它。</p><h2 id="rand-r"><a href="#rand-r" class="headerlink" title="rand_r"></a>rand_r</h2><p>为了在多线程下使用，我们使用rand_r，使用方式和rand是一样的：<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdlib.h&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">rand_r</span><span class="params">(<span class="keyword">unsigned</span> <span class="keyword">int</span> *seedp)</span></span>;</span><br></pre></td></tr></table></figure></p><p>使用示例：<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span><span class="meta-string">&lt;stdlib.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span><span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span><span class="meta-string">&lt;time.h&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">unsigned</span> <span class="keyword">int</span> seed = time(<span class="literal">NULL</span>);</span><br><span class="line">    <span class="keyword">int</span> i = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">while</span>(i &lt; <span class="number">5</span>)<span class="comment">//生成5个随机数</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">"%d "</span>,rand_r(&amp;seed));</span><br><span class="line">        i++;</span><br><span class="line">     &#125;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"\n"</span>);</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>多线程中，多个线程可能几乎同时调用，那它们的种子可能也一样，如果想不一样，还可以将种子设置成和线程id有关。<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">unsigned</span> <span class="keyword">int</span> seed  = time(<span class="literal">NULL</span>)^pthread_self();</span><br></pre></td></tr></table></figure></p><h2 id="random"><a href="#random" class="headerlink" title="random"></a>random</h2><p>通过前面的例子可以发现，rand生成的整数范围是有限的，为了生成更大范围，可以使用random：<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdlib.h&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">long</span> <span class="keyword">int</span> <span class="title">random</span><span class="params">(<span class="keyword">void</span>)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">srandom</span><span class="params">(<span class="keyword">unsigned</span> <span class="keyword">int</span> seed)</span></span>;</span><br></pre></td></tr></table></figure></p><p>random返回的类型为long int，因此在一定程度上，它生成的范围要大得多。另外与rand类似，需要使用srandom函数设置种子。具体的例子就不再放出了。</p><h2 id="生成指定范围随机数"><a href="#生成指定范围随机数" class="headerlink" title="生成指定范围随机数"></a>生成指定范围随机数</h2><p>前面的例子都是生成[1,RAND_MAX]之间的数，如果要生成指定区间的随机数呢？假设a和b不超过int范围以及它们的差值不超过rand的生成范围。</p><h4 id="a-b"><a href="#a-b" class="headerlink" title="[a,b)"></a>[a,b)</h4><p>左闭右开区间，即包含a，不包含：<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">(rand() % (b - a)) + a;</span><br></pre></td></tr></table></figure></p><h4 id="a-b-1"><a href="#a-b-1" class="headerlink" title="[a,b]"></a>[a,b]</h4><p>左闭右闭，即包含a和b：<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">(rand() % (b - a + <span class="number">1</span>)) + a;</span><br></pre></td></tr></table></figure></p><h4 id="a-b-2"><a href="#a-b-2" class="headerlink" title="(a,b]"></a>(a,b]</h4><p>左开右闭，即不包含a，包含b：<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">(rand() % (b-a)) + a + <span class="number">1</span>;</span><br></pre></td></tr></table></figure></p><h4 id="0到1之间的浮点数"><a href="#0到1之间的浮点数" class="headerlink" title="0到1之间的浮点数"></a>0到1之间的浮点数</h4><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">rand()/(<span class="keyword">double</span>)RAND_MAX;</span><br></pre></td></tr></table></figure><h4 id="举例"><a href="#举例" class="headerlink" title="举例"></a>举例</h4><p>生成[2,10)之间的随机数5个：<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//来源：公众号【编程珠玑】</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span><span class="meta-string">&lt;stdlib.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span><span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span><span class="meta-string">&lt;time.h&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    srand(time(<span class="literal">NULL</span>));<span class="comment">//设置随机种子，注意只需要设置一次即可</span></span><br><span class="line">    <span class="keyword">int</span> i = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">int</span> a = <span class="number">2</span>;</span><br><span class="line">    <span class="keyword">int</span> b = <span class="number">10</span>;</span><br><span class="line">    <span class="keyword">while</span>(i &lt; <span class="number">5</span>)<span class="comment">//生成5个随机数</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">"%d "</span>,( rand() % ( b - a ) )+ a);</span><br><span class="line">        i++;</span><br><span class="line">     &#125;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"\n"</span>);</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>记住，通过这些方法生成的都是伪随机数，而一个好的随机算法，它的随机性很强，可能需要根据使用场景去设计具体的算法。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;首先需要说明的是，计算机中生成的随机数严格来说都是伪随机，即非真正的随机数，真正随机数的随机样本不可重现。那么我们来看看代码中有哪些方式可以生成随机数。&lt;br&gt;
    
    </summary>
    
      <category term="C" scheme="https://www.yanbinghu.com/categories/C/"/>
    
    
      <category term="C" scheme="https://www.yanbinghu.com/tags/C/"/>
    
  </entry>
  
  <entry>
    <title>系统编程-文件读写</title>
    <link href="https://www.yanbinghu.com/2019/12/19/8355.html"/>
    <id>https://www.yanbinghu.com/2019/12/19/8355.html</id>
    <published>2019-12-19T13:30:00.000Z</published>
    <updated>2020-10-05T04:01:06.748Z</updated>
    
    <content type="html"><![CDATA[<p>在《<a href="https://www.yanbinghu.com/2019/12/11/54424.html">系统编程-文件IO</a>》中简单介绍了文件I/O的基本流程，无论选项或者参数多么变化多端，其流程大抵相同，不过是获取文件描述符，用描述符进行操作，关闭描述符，三步而已。那么文件读写又是怎样的流程？需要注意什么？<br><a id="more"></a></p><h2 id="write-read"><a href="#write-read" class="headerlink" title="write/read"></a>write/read</h2><p>在说明这些常见出错之前，就必须先了解其基本用法了。需要注意的是，write/read是不带缓冲的，调用一次，写一次。与fwrite/fread有区别，另外write/read为系统调用，频繁地系统调用将会增加开销，可参考《<a href="https://www.yanbinghu.com/2018/05/28/26708.html">库函数和系统调用的区别</a>》。<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;unistd.h&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">ssize_t</span> <span class="title">read</span><span class="params">(<span class="keyword">int</span> fd, <span class="keyword">void</span> *buf, <span class="keyword">size_t</span> count)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">ssize_t</span> <span class="title">write</span><span class="params">(<span class="keyword">int</span> fd, <span class="keyword">const</span> <span class="keyword">void</span> *buf, <span class="keyword">size_t</span> count)</span></span>;</span><br></pre></td></tr></table></figure></p><p>参数解释：</p><ul><li>fd  文件描述符，这个应该不用多做解释</li><li>buf 要写入的内容，或者读出内容存储的buf，合适的大小非常关键</li><li>count 读或写的内容大小</li></ul><p>这里有两点需要注意一下。</p><p>返回值为ssize_t类型，因为它的返回值可以为负，表示出错，有趣的是这样一来使得其能表示的读写字节范围少了近一半。<br>返回大于0，表示读或写入对应的字节数。对于read，返回0表示到文件结尾。</p><p>另外，我们还注意到，write函数的第二个参数由const修饰。为什么要使用const来修饰？</p><p>很显然，在写的过程中，write函数不应该对buf的内容进行修改，它仅仅是从buf中读取罢了。这里在编码时常用的设计，如果不希望该函数修改其内容，则加上const限定符。const详细说明参考《<a href="https://www.yanbinghu.com/2019/01/28/7442.html">const关键字到底该怎么用？</a>》。</p><p>那么返回的读写大小，和参数里的count大小有何区别？前者是真实读写的字节数，而后者是期望读写的字节数。举个简单的例子，文件中有16字节内容，而你尝试读64字节，自然最终只会读到16字节。</p><h2 id="正常读写"><a href="#正常读写" class="headerlink" title="正常读写"></a>正常读写</h2><p>正常读写的例子如下：<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//来源：公众号【编程珠玑】</span></span><br><span class="line"><span class="comment">//博客：https://www.yanbinghu.com</span></span><br><span class="line"><span class="comment">//file.c</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;unistd.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;fcntl.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/stat.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/types.h&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">char</span> writeBuf[] = <span class="string">"https://www.yanbinghu.com"</span>;</span><br><span class="line">    <span class="keyword">char</span> readBuf[<span class="number">128</span>] = &#123;<span class="number">0</span>&#125;;</span><br><span class="line">    <span class="comment">/*可读可写，不存在时创建，有内容时截断*/</span></span><br><span class="line">    <span class="keyword">int</span> fd = open(<span class="string">"test.txt"</span>,O_RDWR | O_CREAT | O_TRUNC);</span><br><span class="line">    <span class="keyword">if</span>(<span class="number">-1</span> == fd)</span><br><span class="line">    &#123;</span><br><span class="line">        perror(<span class="string">"open failed"</span>);</span><br><span class="line">        <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">/*写内容*/</span></span><br><span class="line">    <span class="keyword">ssize_t</span> wLen = write(fd,writeBuf,<span class="keyword">sizeof</span>(writeBuf));</span><br><span class="line">    <span class="keyword">if</span>(wLen &lt; <span class="number">0</span>)</span><br><span class="line">    &#123;</span><br><span class="line">        perror(<span class="string">"write failed"</span>);</span><br><span class="line">        close(fd);</span><br><span class="line">        <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"write len:%ld\n"</span>,wLen);</span><br><span class="line">    <span class="keyword">ssize_t</span> rLen = read(fd,readBuf,<span class="keyword">sizeof</span>(readBuf));</span><br><span class="line">    <span class="keyword">if</span>(rLen &lt; <span class="number">0</span>)</span><br><span class="line">    &#123;</span><br><span class="line">        perror(<span class="string">"read failed"</span>);</span><br><span class="line">        close(fd);</span><br><span class="line">        <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    readBuf[<span class="keyword">sizeof</span>(readBuf)<span class="number">-1</span>] = <span class="number">0</span>;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"read content:%s\n"</span>,readBuf);</span><br><span class="line">    close(fd);</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>编译运行，然后你就会惊喜地发现，结果并不是如你想地那样：<br><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> gcc -o writeFile file.c</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> ./writeFile</span></span><br><span class="line">write len:26</span><br><span class="line">read content:</span><br></pre></td></tr></table></figure></p><p>我们查看文件可以看到内容已经写进去了，但是读取出来地内容却是空！</p><p>这是为何？<br>理解这个问题需要理解文件描述符和偏移量。</p><h4 id="文件描述符"><a href="#文件描述符" class="headerlink" title="文件描述符"></a>文件描述符</h4><p>文件描述符虽然只是一个整型值，但它只是一个索引值，它指向了该进程打开文件的记录表。还记得常说的“一切皆文件”吗？实际上，即使你每打开一个TCP链接，都会有一个对应的文件描述符。这个记录表中包含了很多与文件相关地信息，例如文件偏移量，inode，状态标志等等。</p><p>而你每一次进行读写，都会影响所谓地文件偏移量。</p><p>因此你在第一次进行写之后，文件偏移量类似于下面这样：</p><div class="table-container"><table><thead><tr><th><a href="https://www.yanbinghu.com\0">https://www.yanbinghu.com\0</a></th><th></th><th></th></tr></thead><tbody><tr><td></td><td>&uarr;</td></tr></tbody></table></div><p>那么你进行第一次读的时候，文件偏移已经到文件的末尾了（此时函数返回值为0），所以你肯定读不出任何内容，因此你需要移动偏移指针。</p><h4 id="设置偏移量"><a href="#设置偏移量" class="headerlink" title="设置偏移量"></a>设置偏移量</h4><p>为了读取写入后的内容，我们必须要设置偏移量，设置成像下面这样：</p><div class="table-container"><table><thead><tr><th><a href="https://www.yanbinghu.com\0">https://www.yanbinghu.com\0</a></th><th></th><th></th></tr></thead><tbody><tr><td>&uarr;</td><td></td></tr></tbody></table></div><p>有人可能会好奇，这最后为什么还有一个\0？很显然，它被自动加上了，具体原因可以参考《<a href="https://www.yanbinghu.com/2019/08/19/18180.html">NULL,0,’0’你真的分清了吗</a>》。</p><p>还有人会问，你怎么看出有一个\0？用od命令看一下就知道了。<br><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> od -c test.txt</span></span><br><span class="line">0000000   h   t   t   p   s   :   /   /   w   w   w   .   y   a   n   b</span><br><span class="line">0000020   i   n   g   h   u   .   c   o   m  \0</span><br><span class="line">0000032</span><br></pre></td></tr></table></figure></p><p>现在看到了吧。</p><p>为了设置偏移量，我们需要用到函数lseek：<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;unistd.h&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">off_t</span> <span class="title">lseek</span><span class="params">(<span class="keyword">int</span> fd, <span class="keyword">off_t</span> offset, <span class="keyword">int</span> whence)</span></span>;</span><br></pre></td></tr></table></figure></p><p>成功返回新的文件偏移量，出错返回-1。</p><p>有必要对参数进行解释</p><ul><li>offset 相对于whence的偏移量</li><li>whence 相对位置</li></ul><p>其中whence有三个值</p><ul><li>SEEK_SET  文件开始处</li><li>SEEK_CUR  当前位置</li><li>SEEK_END   文件末尾</li></ul><p>举个例子，假设当前offset为-4，whence为SEEK_CUR，那么当写完内容，并设置该选项后的文件偏移位置如下：</p><div class="table-container"><table><thead><tr><th><a href="https://www.yanbinghu" target="_blank" rel="noopener">https://www.yanbinghu</a>.</th><th>com</th><th></th></tr></thead><tbody><tr><td></td><td>&uarr;</td></tr></tbody></table></div><p>注意，offset是可以为负的。</p><p>说白了可以设置偏移位置，而设置可以相对三个位置，开头，当前和结尾。</p><h4 id="读取写入的内容"><a href="#读取写入的内容" class="headerlink" title="读取写入的内容"></a>读取写入的内容</h4><p>好了，为了读取到我们写入的内容，我们已经知道怎么做了，就是设置偏移量在文件开头，即在读之前加上下面的语句：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">lseek(fd, 0, SEEK_SET);//注意检查返回值</span><br></pre></td></tr></table></figure></p><p>然后再次编译运行：<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">write len:<span class="number">26</span></span><br><span class="line">read content:https:<span class="comment">//www.yanbinghu.com</span></span><br></pre></td></tr></table></figure></p><p>如你所愿！</p><h2 id="常见报错"><a href="#常见报错" class="headerlink" title="常见报错"></a>常见报错</h2><p>使用不当或者出错的时候会有错误信息，这在编码的时候就需要注意检查。</p><h4 id="Bad-file-descriptor"><a href="#Bad-file-descriptor" class="headerlink" title="Bad file descriptor"></a>Bad file descriptor</h4><p>通常使用了一个并不合法的文件描述符，例如，该文件描述符已经关闭。通常你可以通过下面的命令来观察文件描述符的打开情况：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ ls -al /proc/`pidof procName`/fd/</span><br></pre></td></tr></table></figure></p><p>这里的procName是你正在运行的程序名。</p><p>也有可能是你打开模式不对，例如，以只读方式打开，却尝试写。</p><h4 id="Interrupted-system-call"><a href="#Interrupted-system-call" class="headerlink" title="Interrupted system call"></a>Interrupted system call</h4><p>通常是在读写过程中被中断，常见的如对socket进行读写时，链接被意外中断，或者读写时，进程被中断等等。</p><h4 id="File-exists"><a href="#File-exists" class="headerlink" title="File exists"></a>File exists</h4><p>通常在你想创建一个文件，但是文件已经存在的情况。</p><h4 id="No-such-file-or-directory"><a href="#No-such-file-or-directory" class="headerlink" title="No such file or directory"></a>No such file or directory</h4><p>就如字面意思，通常是文件或者目录不存在，也许你使用了O_CREATE标志，但是如果你的目录不存在，文件也无法创建成功。</p><p>还有一种情况是，你已经打开了该文件，程序执行过程中，该文件又被人删除了，删除后又创建了一个文件名一样的文件，这样的情况下，也有可能会提示该错误。</p><h4 id="Too-many-open-fileswrite"><a href="#Too-many-open-fileswrite" class="headerlink" title="Too many open fileswrite"></a>Too many open fileswrite</h4><p>进程打开的文件过多。一个进程打开的文件数量是有限的，具体可以通过：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ ulimit -n</span><br><span class="line">65535</span><br></pre></td></tr></table></figure></p><p>至于当前已经打开了多少，可以这样统计：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ ls -l /proc/`pidof proName`/fd/ |wc -l</span><br></pre></td></tr></table></figure></p><p>proName为你的进程名。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>一些常见错误中很多涉及到网络的读写，这里暂时没有提及。</p><p>一般情况，不会用同一个文件描述符对文件进行既读又写，一旦出现这样的场景时，需要注意偏移量的设置。虽然本文的I/O函数不带缓冲，但是读写时，选择合适的buf大小也非常关键。</p><p>另外编程中也有以下建议：</p><ul><li>检查接口的返回值，处理出错场景</li><li>对于不期望被修改内容的参数，添加const限定符</li><li>善用man手册</li></ul>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;在《&lt;a href=&quot;https://www.yanbinghu.com/2019/12/11/54424.html&quot;&gt;系统编程-文件IO&lt;/a&gt;》中简单介绍了文件I/O的基本流程，无论选项或者参数多么变化多端，其流程大抵相同，不过是获取文件描述符，用描述符进行操作，关闭描述符，三步而已。那么文件读写又是怎样的流程？需要注意什么？&lt;br&gt;
    
    </summary>
    
      <category term="C" scheme="https://www.yanbinghu.com/categories/C/"/>
    
    
      <category term="C" scheme="https://www.yanbinghu.com/tags/C/"/>
    
  </entry>
  
  <entry>
    <title>系统编程-多线程(1)</title>
    <link href="https://www.yanbinghu.com/2019/12/17/19410.html"/>
    <id>https://www.yanbinghu.com/2019/12/17/19410.html</id>
    <published>2019-12-17T14:30:00.000Z</published>
    <updated>2020-10-05T04:01:06.748Z</updated>
    
    <content type="html"><![CDATA[<p>多线程，作为一个开发者，这个名词应该不陌生。我在《<a href="https://www.yanbinghu.com/2018/09/07/47517.html">对进程和线程的一些总结</a>》中也有介绍，这里就不详述。<br><a id="more"></a></p><h2 id="为什么要用多线程"><a href="#为什么要用多线程" class="headerlink" title="为什么要用多线程"></a>为什么要用多线程</h2><p>很显然，多线程能够同时执行多个任务。举个例子，你打开某视频播放器，点击下载某个视频，然后你发现这个时候一直在下载，其他啥都干不了，那你肯定骂*。所以在这种情况下，可以使用多线程，让下载任务继续，同时也能继续其他操作。</p><p>作为一个包工头，一堆砖要搬，但是就一个人，可是你只能搬这么多，怎么办？多找几个人一起搬呗，但是其他人就也需要付工钱，没关系，能早点干完也就行了，反正总体工钱差不多。</p><p>同样的，如果有一个任务特别耗时，而这个任务可以拆分为多个任务，那么就可以让每个线程去执行一个任务，这样任务就可以更快地完成了。</p><h2 id="代价"><a href="#代价" class="headerlink" title="代价"></a>代价</h2><p>听起来都很好，但是多线程是有代价的。由于它们“同时”进行任务，那么它们任务的有序性就很难保障，而且一旦任务相关，它们之间可能还会竞争某些公共资源，造成死锁等问题。</p><h2 id="绑核"><a href="#绑核" class="headerlink" title="绑核"></a>绑核</h2><p>通过下面的命令可将进程proName程序绑在1核运行：<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">taskset -c <span class="number">1</span> ./proName</span><br></pre></td></tr></table></figure></p><p>而如果只绑定了一个核，那么同一时刻，只有一个线程在运行，而线程之间的切换又会消耗资源，那么这种情况下反而会导致性能降低。</p><p>另外一种情况，就是设置的线程数大于总的逻辑CPU数：<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ cat /proc/cpuinfo| grep <span class="string">"processor"</span>| wc -l</span><br><span class="line"><span class="number">8</span></span><br></pre></td></tr></table></figure></p><p>这样的情况下，设置更多的线程并不会提高处理速度。</p><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>优点：</p><ul><li>更快，加快处理任务</li><li>更强，同时处理多任务</li></ul><p>缺点：</p><ul><li>难控制，编程困难</li><li>不当使用降低性能，线程切换</li><li>bug难定位，资源竞争</li></ul><h2 id="如何创建多线程"><a href="#如何创建多线程" class="headerlink" title="如何创建多线程"></a>如何创建多线程</h2><p>普通的进程通常只有一个线程，称为主线程。</p><p>创建线程需要使用下面的函数：<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;pthread.h&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">pthread_create</span><span class="params">(<span class="keyword">pthread_t</span> *thread, <span class="keyword">const</span> <span class="keyword">pthread_attr_t</span> *attr,</span></span></span><br><span class="line"><span class="function"><span class="params">                          <span class="keyword">void</span> *(*start_routine) (<span class="keyword">void</span> *), <span class="keyword">void</span> *arg)</span></span>;</span><br></pre></td></tr></table></figure></p><p>参数有必要做一下说明</p><ul><li>thread 线程ID指针，创建成功时，会保存在此</li><li>attr 线程属性，控制线程的一些行为</li><li>start_routine 线程运行起始地址，是一个函数指针</li><li>arg 函数的参数，只有一个参数，因此多个参数需要打包在一起</li></ul><p>创建成功时，返回0，否则出错。<br>看到了吗，到处都有void*的身影（参考《<a href="https://www.yanbinghu.com/2019/12/15/19682.html">void*是什么玩意</a>》）。</p><p>使用时注意包含头文件<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">#include &lt;pthread.h&gt;</span><br></pre></td></tr></table></figure></p><p>，并且在链接时加上-lpthread，因此它不在libc库中。在《<a href="https://www.yanbinghu.com/2018/10/06/46212.html">一个奇怪的链接问题</a>》中提到，对于非glibc库中的库函数，都需要显式链接对应的库。</p><p>试着写一个简单的多线程程序，简单起见，我们暂时不设置任何属性，将attr字段设置为NULL：<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//来源：公众号【编程珠玑】</span></span><br><span class="line"><span class="comment">//main.c</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;pthread.h&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">void</span> *<span class="title">myThread</span><span class="params">(<span class="keyword">void</span> *id)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"thread run,value is %d\n"</span>,*(<span class="keyword">int</span>*)id);</span><br><span class="line"><span class="comment">//return NULL; 这种方式也可以退出线程</span></span><br><span class="line">pthread_exit((<span class="keyword">void</span>*)<span class="number">0</span>);<span class="comment">//退出线程</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">pthread_t</span> tid ;</span><br><span class="line"><span class="keyword">int</span> i = <span class="number">10</span>;</span><br><span class="line"><span class="keyword">int</span> status = pthread_create(&amp;tid,<span class="literal">NULL</span>,myThread,(<span class="keyword">void</span>*)&amp;i);</span><br><span class="line"><span class="keyword">if</span>(status &lt; <span class="number">0</span> )</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"crete failed\n"</span>);</span><br><span class="line">&#125;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"main func finished\n"</span>);</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>编译运行：<br><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> gcc -o main main.c -lpthread</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> ./main</span></span><br><span class="line">main func finished</span><br></pre></td></tr></table></figure></p><p>发现运行的结果并不如我们预期那样，就好像线程没有执行一样。</p><p>原因在于，如果主线程退出了，那么其他线程也会退出。所谓，皮之不存，毛将焉附，所有线程都共同使用很多资源，相关内容也可以从《<a href="https://www.yanbinghu.com/2018/09/07/47517.html">对进程和线程的一些总结</a>》中了解到。<br>如何改进呢？我们可以等线程执行完啊，于是，在主线程退出前sleep:<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">pthread_t</span> tid ;</span><br><span class="line"><span class="keyword">int</span> i = <span class="number">10</span>;</span><br><span class="line"><span class="keyword">int</span> status = pthread_create(&amp;tid,<span class="literal">NULL</span>,myThread,(<span class="keyword">void</span>*)&amp;i);</span><br><span class="line"><span class="keyword">if</span>(status &lt; <span class="number">0</span> )</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"crete failed\n"</span>);</span><br><span class="line">&#125;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"main func finished\n"</span>);</span><br><span class="line">    sleep(<span class="number">1</span>);</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>这样就好了（注意添加头文件<figure class="highlight plain"><figcaption><span><unistd.h>```）。</unistd.h></span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">```</span><br><span class="line">main func finished</span><br><span class="line">thread run,value is 10</span><br></pre></td></tr></table></figure></p><p>但是你会发现，<figure class="highlight plain"><figcaption><span>func finished```可能会先打印。这也就呼应了文章标题。</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">但是转念一想，如果线程执行的时间超过一秒呢，难道就要sleep更长时间吗？而很多时候甚至根本不知道线程要执行多长时间，那怎么办呢？</span><br><span class="line"></span><br><span class="line">还可以使用：</span><br><span class="line">```c</span><br><span class="line">int pthread_join(pthread_t thread, void **retval);</span><br></pre></td></tr></table></figure></p><p>thread是前面获得的线程id，而retval包含了线程的返回信息，假设我们完全不关心线程的退出状态，那么可以设置为NULL。</p><p>修改代码如下：<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">pthread_t</span> tid ;</span><br><span class="line"><span class="keyword">int</span> i = <span class="number">10</span>;</span><br><span class="line"><span class="keyword">int</span> status = pthread_create(&amp;tid,<span class="literal">NULL</span>,myThread,(<span class="keyword">void</span>*)&amp;i);</span><br><span class="line"><span class="keyword">if</span>(status &lt; <span class="number">0</span> )</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"crete failed\n"</span>);</span><br><span class="line">&#125;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"main func finished\n"</span>);</span><br><span class="line">    pthread_join(tid,<span class="literal">NULL</span>);</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>这种情况同样可以达到目的，pthread_join，会阻塞程序，直到线程退出（前提是线程为非分离线程）。</p><h2 id="线程终止"><a href="#线程终止" class="headerlink" title="线程终止"></a>线程终止</h2><p>以下几种情况下，线程会终止</p><ul><li>线程函数返回</li><li>调用pthread_exit，主线程调用无碍</li><li>调用pthread_cancel</li><li>调用exit，或者主线程退出，所有线程终止<h2 id="注意"><a href="#注意" class="headerlink" title="注意"></a>注意</h2>假如修改下面的代码：<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">pthread_t</span> tid;</span><br><span class="line"><span class="keyword">int</span> i = <span class="number">10</span>;</span><br><span class="line"><span class="keyword">int</span> status = pthread_create(&amp;tid,<span class="literal">NULL</span>,myThread,(<span class="keyword">void</span>*)&amp;i);</span><br><span class="line"><span class="keyword">if</span>(status &lt; <span class="number">0</span> )</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"crete failed\n"</span>);</span><br><span class="line">&#125;</span><br><span class="line">i = <span class="number">6</span>;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"main func finished\n"</span>);</span><br><span class="line">pthread_join(tid,<span class="literal">NULL</span>);</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ul><p>在创建线程后，修改i的值，你会发现在线程中打印的不会是10，而是6。</p><p>也就是说，创建线程的时候，传入的参数必须确保其使用这个参数时，参数没有被修改，否则的话，拿到的将是错误的值，</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>本文通过一些小例子，简单介绍了线程概念，对于绑核，多线程同步等问题均一笔带过，将在后面的文章中继续介绍。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;多线程，作为一个开发者，这个名词应该不陌生。我在《&lt;a href=&quot;https://www.yanbinghu.com/2018/09/07/47517.html&quot;&gt;对进程和线程的一些总结&lt;/a&gt;》中也有介绍，这里就不详述。&lt;br&gt;
    
    </summary>
    
      <category term="C" scheme="https://www.yanbinghu.com/categories/C/"/>
    
    
      <category term="C" scheme="https://www.yanbinghu.com/tags/C/"/>
    
  </entry>
  
  <entry>
    <title>void*到底是什么玩意？</title>
    <link href="https://www.yanbinghu.com/2019/12/15/19682.html"/>
    <id>https://www.yanbinghu.com/2019/12/15/19682.html</id>
    <published>2019-12-15T13:10:00.000Z</published>
    <updated>2020-10-05T04:01:06.748Z</updated>
    
    <content type="html"><![CDATA[<p>说到C就不得不提指针，而一提到指针，有一个是比较特殊的，那就是void*。</p><p>void*到底是怎样的存在？</p><a id="more"></a><h2 id="指针类型的含义"><a href="#指针类型的含义" class="headerlink" title="指针类型的含义"></a>指针类型的含义</h2><p>在说明void*之前，先了解一下普通指针类型的含义。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//来源：公众号【编程珠玑】</span></span><br><span class="line"><span class="comment">//main.c</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> a[] = &#123;<span class="number">0x01020304</span>,<span class="number">2019</span>&#125;;</span><br><span class="line">    <span class="keyword">int</span> *b = a;</span><br><span class="line">    <span class="keyword">char</span> *c = (<span class="keyword">char</span>*)&amp;a[<span class="number">0</span>];</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"b+1:%d\n"</span>,*(b+<span class="number">1</span>));</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"c+1:%d\n"</span>,*(c+<span class="number">1</span>));</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上面的输出结果为：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">b+1:2019</span><br><span class="line">c+1:3</span><br></pre></td></tr></table></figure></p><p>对于上面的结果，也许你并不感到意外。如果你的疑问是为什么不是2而是3，那么建议你看看《<a href="https://www.yanbinghu.com/2018/10/02/25450.html">理一理字节序的事</a>》。同样是指针类型，b和c有什么区别？<br>一个是指向整型的指针，一个是指向char型的指针，当它们执行算术运算时，它们的步长就是对应类型占用空间大小。<br>即<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">b + <span class="number">1</span> <span class="comment">//移动sizeof(int)字节</span></span><br></pre></td></tr></table></figure></p><div class="table-container"><table><thead><tr><th>04</th><th>03</th><th>02</th><th>01</th><th>2019</th></tr></thead><tbody><tr><td>字节0</td><td>字节1</td><td>字节2</td><td>字节3</td><td>字节4~7</td></tr><tr><td></td><td></td><td></td><td></td><td>&uarr;</td></tr></tbody></table></div><p>指针移动4个字节后，指向的就是2019了，解引用自然得到2019。</p><p>而对于c<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">c + <span class="number">1</span> <span class="comment">//移动sizeof(char)字节</span></span><br></pre></td></tr></table></figure></p><p>它的指向如下：</p><div class="table-container"><table><thead><tr><th>04</th><th>03</th><th>02</th><th>01</th><th>2019</th></tr></thead><tbody><tr><td>字节0</td><td>字节1</td><td>字节2</td><td>字节3</td><td>字节4~7</td></tr><tr><td></td><td>&uarr;</td><td></td><td></td></tr></tbody></table></div><p>解引用之后，自然得到3。</p><h2 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h2><p>各种类型之间没有本质区别，只是解释内存中的数据方式不同。</p><p>例如，对于int型指针b，解引用时，会解析4字节，算术运算时，也是以该类型占用空间大小为单位，所以b+1，移动4字节，解引用，处理4字节内容，得到2019。</p><p>对于char型指针c，解引用时，会解析1个字节，算术运算时，也是以sizeof(char)为单位，所以c+1，移动一字节，解引用，处理1字节，得到03。</p><p>所以像下面这样的操作：<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">char</span> a[] = &#123;<span class="number">01</span>,<span class="number">02</span>,<span class="number">03</span>,<span class="number">04</span>&#125;;</span><br><span class="line"><span class="keyword">int</span> *b = (<span class="keyword">int</span>*)a+<span class="number">2</span>;</span><br></pre></td></tr></table></figure></p><p>如果你试图解引用b，即*b，就可能遇到无法预料的问题，因为将会访问非法内存位置。</p><p>a+2，移动sizeof(char)字节，指向03，此时按照int类型指针解引用，由于int类型解引用会处理4字节内存，但是后面已经没有属于数组a的合法内容了，因此可能出错。</p><h2 id="指针占用空间大小"><a href="#指针占用空间大小" class="headerlink" title="指针占用空间大小"></a>指针占用空间大小</h2><p>正由于它们没有本质区别，它们占用空间大小在同一个程序中都是固定的，对于32位程序，占用4字节空间，64位占用8字节，而正因如此，64位程序理论能使用的内存是足够大的，而32位程序理论上能使用的不过4G（2^（4*8bit)），再加上内核空间的使用，真正能用到的可能就3G左右。</p><p>如果你的系统是64位的，那么默认情况下，编译出来的程序也是64位的。如果你想编译为32位，可以使用-m32参数：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ gcc -m32 -o main main.c</span><br></pre></td></tr></table></figure></p><p>如何确定是多少位的程序：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ readelf -h main</span><br><span class="line">Class:                             ELF32</span><br></pre></td></tr></table></figure></p><p>上面的ELF32，表明了它是32位程序。或者可以看Machine字段：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Machine:                           Intel 80386</span><br></pre></td></tr></table></figure></p><h2 id="void"><a href="#void" class="headerlink" title="void*"></a>void*</h2><p>说回void*，前面说了，指针的类型不过是解释数据的方式不同罢了，这样的道理也可用于很多场合的强制类型转换，例如将int类型指针转换为char型指针，并不会改变内存的实际内容，只是修改了解释方式而已。而void <em>是一种无类型指针，任何类型指针都可以转为void\</em>，它无条件接受各种类型。</p><p>而既然是无类型指针，那么就不要尝试做下面的事情：</p><ul><li>解引用</li><li>算术运算</li></ul><p>由于不知道其解引用操作的内存大小，以及算术运算操作的大小，因此它的结果是未知的。<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> a = <span class="number">10</span>;</span><br><span class="line">    <span class="keyword">int</span> *b = &amp;a;</span><br><span class="line">    <span class="keyword">void</span> *c = b;</span><br><span class="line">    *c;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>编译警告如下：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">warning: dereferencing ‘void *’ pointer</span><br></pre></td></tr></table></figure></p><h2 id="如何使用"><a href="#如何使用" class="headerlink" title="如何使用"></a>如何使用</h2><p>既然如此，那么void*有什么用呢？</p><p>实际上我们在很多接口中都会发现它们的参数类型都是void*,例如:<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">ssize_t</span> <span class="title">read</span><span class="params">(<span class="keyword">int</span> fd, <span class="keyword">void</span> *buf, <span class="keyword">size_t</span> count)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">void</span> *<span class="title">memcpy</span><span class="params">(<span class="keyword">void</span> *dest, <span class="keyword">const</span> <span class="keyword">void</span> *src, <span class="keyword">size_t</span> n)</span></span>;</span><br></pre></td></tr></table></figure></p><p>为何要如此设计？因为对于这种通用型接口，你不知道用户的数据类型是什么，但是你必须能够处理用户的各种类型数据，因而会使用void*。void*能包容地接受各种类型的指针。</p><p>也就是说，如果你期望接口能够接受任何类型的参数，你可以使用void*类型。</p><p>但是在具体使用的时候，你必须转换为具体的指针类型。例如，你传入接口的是int*，那么你在使用的时候就应该按照int*使用。</p><h2 id="注意"><a href="#注意" class="headerlink" title="注意"></a>注意</h2><p>使用void*需要特别注意的是，你必须清楚原始传入的是什么类型，然后转换成对应类型。例如，你准备使用库函数qsort进行排序：<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">qsort</span><span class="params">(<span class="keyword">void</span> *base,<span class="keyword">size_t</span> nmemb,<span class="keyword">size_t</span> size , <span class="keyword">int</span>(*compar)(<span class="keyword">const</span> <span class="keyword">void</span> *,<span class="keyword">const</span> <span class="keyword">void</span> *))</span></span>;</span><br></pre></td></tr></table></figure></p><p>它的第三个参数就是比较函数，它接受的参数都是const void*，如果你的比较对象是一个结构体类型，那么你自己在实现compar函数的时候，也必须是转换为该结构体类型使用。</p><p>举个例子，你要实现学生信息按照成绩比较：<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//来源：公众号【编程珠玑】</span></span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">student_tag</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line">    <span class="keyword">char</span> name[STU_NAME_LEN];  <span class="comment">//学生姓名</span></span><br><span class="line">    <span class="keyword">unsigned</span> <span class="keyword">int</span> id;          <span class="comment">//学生学号</span></span><br><span class="line">    <span class="keyword">int</span> score;                <span class="comment">//学生成绩</span></span><br><span class="line">&#125;<span class="keyword">student_t</span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">studentCompare</span><span class="params">(<span class="keyword">const</span> <span class="keyword">void</span> *stu1,<span class="keyword">const</span> <span class="keyword">void</span> *stu2)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">　　<span class="comment">/*强转成需要比较的数据结构*/</span></span><br><span class="line">    <span class="keyword">student_t</span> *value1 = (<span class="keyword">student_t</span>*)stu1;</span><br><span class="line">    <span class="keyword">student_t</span> *value2 = (<span class="keyword">student_t</span>*)stu2;</span><br><span class="line">    <span class="keyword">return</span> value1-&gt;score-value2-&gt;score;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>在将其传入<code>studentCompare</code>函数后，必须转换为其对应的类型进行处理。<br>更多函数指针相关内容可以参考《<a href="https://www.yanbinghu.com/2019/01/03/3593.html">高级指针话题-函数指针</a>》。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>void*很强大，但是一定要在合适的时候使用；同时强转很逆天，但是一定要注意前后的类型是否真的能正确转换。</p><p>通俗地说void*：</p><ul><li>这里有一片内存数据，我也不知道什么类型，给你了，你自己想怎么用怎么用吧，不过要用对奥！</li><li>我这里什么类型都能处理，你给我一片内存数据就可以了</li></ul>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;说到C就不得不提指针，而一提到指针，有一个是比较特殊的，那就是void*。&lt;/p&gt;
&lt;p&gt;void*到底是怎样的存在？&lt;/p&gt;
    
    </summary>
    
      <category term="C" scheme="https://www.yanbinghu.com/categories/C/"/>
    
    
      <category term="C" scheme="https://www.yanbinghu.com/tags/C/"/>
    
  </entry>
  
</feed>
