<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>AnSwErYWJ&#39;s Blog</title>
  
  <subtitle>Sharing is a virtue</subtitle>
  <link href="https://answerywj.com/atom.xml" rel="self"/>
  
  <link href="https://answerywj.com/"/>
  <updated>2023-03-20T06:26:35.318Z</updated>
  <id>https://answerywj.com/</id>
  
  <author>
    <name>AnSwErYWJ</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>Linux下core文件的生成和使用</title>
    <link href="https://answerywj.com/2022/12/20/generate-and-usage-of-core-in-linux/"/>
    <id>https://answerywj.com/2022/12/20/generate-and-usage-of-core-in-linux/</id>
    <published>2022-12-20T09:17:11.000Z</published>
    <updated>2023-03-20T06:26:35.318Z</updated>
    
    <content type="html"><![CDATA[<p>本文对作者<code>2018</code>年的博文<a href="https://answerywj.com/2018/03/07/usage-of-core-in-linux/">Linux下core文件使用</a>做了一系列更新，特别是针对<code>Ubuntu 20.04</code>下<code>core</code>文件生成异常做了分析与解决。</p><span id="more"></span><h2 id="什么是core文件"><a href="#什么是core文件" class="headerlink" title="什么是core文件"></a>什么是core文件</h2><p>在<code>Linux</code>下遇到程序异常退出或者中止，操作系统通常会把程序当前的工作状况存储在一个名为<code>core</code>的文件中，其中包含了程序运行时的内存、寄存器和堆栈指针等信息，格式为<code>ELF</code>，这个过程叫做<code>coredump</code>，又称作核心转储。</p><p>通过工具分析这个文件，我们可以定位到程序异常退出或者终止时相应的堆栈调用等信息。</p><h2 id="如何生成core文件"><a href="#如何生成core文件" class="headerlink" title="如何生成core文件"></a>如何生成core文件</h2><h3 id="查看coredump是否生效"><a href="#查看coredump是否生效" class="headerlink" title="查看coredump是否生效"></a>查看coredump是否生效</h3><figure class="highlight plaintext"><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">$ ulimit -a</span><br><span class="line">...</span><br><span class="line">-c: core file size (blocks)         0</span><br><span class="line">...</span><br></pre></td></tr></table></figure><p><code>0</code> 表示<code>core</code>文件大小限制为<code>0</code>，不允许写入，所以无法生成<code>core</code>文件，需要修改为正数大小或<code>unlimited</code>才可使<code>coredump</code>生效。</p><h3 id="修改core文件大小的限制"><a href="#修改core文件大小的限制" class="headerlink" title="修改core文件大小的限制"></a>修改core文件大小的限制</h3><p>取消<code>core</code>文件大小限制：</p><figure class="highlight plaintext"><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">ulimit -c unlimited</span><br><span class="line">ulimit -a</span><br><span class="line">...</span><br><span class="line">-c: core file size (blocks)         unlimited</span><br><span class="line">...</span><br></pre></td></tr></table></figure><p>也可以对<code>core</code>文件的大小进行有效限制，单位为<code>blocks</code>，一般<code>1 block=512 bytes</code>，设置太小可能导致不会生成文件：</p><figure class="highlight plaintext"><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">$ ulimit -c 1024</span><br><span class="line">$ ulimit -a</span><br><span class="line">...</span><br><span class="line">-c: core file size (blocks)         1024</span><br><span class="line">...</span><br></pre></td></tr></table></figure><blockquote><p>上面对<code>core</code>文件的操作仅对当前生效，若需要永久生效，则要将相应操作写入<code>/etc/profile</code>。</p></blockquote><h3 id="设置core文件存储路径"><a href="#设置core文件存储路径" class="headerlink" title="设置core文件存储路径"></a>设置core文件存储路径</h3><p><code>core</code>文件默认存储在程序的工作目录，可以通过命令<code>cat /proc/sys/kernel/core_pattern </code>查看。</p><p>在文件<code>/etc/sysctl.conf</code>末尾加入如下信息，可以指定<code>core</code>文件的存储路径：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kernel.core_pattern=/dumpdir/core_%e_%p_%t</span><br></pre></td></tr></table></figure><p>控制<code>core</code>文件的文件名中是否添加<code>pid</code>作为扩展：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">echo &quot;1&quot; &gt; /proc/sys/kernel/core_uses_pid  </span><br></pre></td></tr></table></figure><blockquote><p><code>/proc/sys/kernel/core_uses_pid</code>这个文件的值若为１，则无论是否配置<code>%p</code>，最后生成的<code>core</code>文件都会添加<code>pid</code>。</p></blockquote><p>通常情况下，重启即可生效。</p><p>附<code>core</code>文件命名使用的参数列表：</p><figure class="highlight plaintext"><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">%p - insert pid into filename  # 添加 pid </span><br><span class="line">%u - insert current uid into filename  # 添加当前 uid </span><br><span class="line">%g - insert current gid into filename  # 添加当前 gid </span><br><span class="line">%s - insert signal that caused the coredump into the filename  # 添加导致产生 core 的信号 </span><br><span class="line">%t - insert UNIX time that the coredump occurred into filename  # 添加 core 文件生成时的 unix 时间 </span><br><span class="line">%h - insert hostname where the coredump happened into filename  # 添加主机名 </span><br><span class="line">%e - insert coredumping executable name into filename  # 添加命令名</span><br></pre></td></tr></table></figure><h3 id="关闭apport-service服务"><a href="#关闭apport-service服务" class="headerlink" title="关闭apport.service服务"></a>关闭apport.service服务</h3><p><code>Ubuntu 20.04</code>中，执行完上述操作，会发现还是无法在指定目录生成<code>core</code>文件。</p><p>查看<code>core</code>文件存储路径：</p><figure class="highlight plaintext"><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/sys/kernel/core_pattern</span><br><span class="line">|/usr/share/apport/apport %p %s %c %d %P %E</span><br></pre></td></tr></table></figure><p>发现<code>core</code>文件存储路径并非自己设置的，而是由管道交给了一个<code>apport</code>的程序，通过查询可知其是<code>Ubuntu</code>官方为了自动收集错误，生成程序崩溃报告的一个服务，即<code>apport.service</code>。</p><p>我们可以关闭<code>apport.service</code>这个服务：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo service apport stop</span><br></pre></td></tr></table></figure><blockquote><p>使用<code>sudo service apport start</code>可以开启这个服务。</p></blockquote><p>如果这个命令无效的话，可以修改<code>/etc/default/apport</code>文件，将<code>enabled</code>改成<code>0</code>。</p><p>如上，可以在指定路径生成<code>core</code>文件。</p><h2 id="使用core文件调试"><a href="#使用core文件调试" class="headerlink" title="使用core文件调试"></a>使用core文件调试</h2><p>可以使用<code>gdb</code>对<code>core</code>文件进行调试：</p><figure class="highlight plaintext"><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">$ gdb a.out</span><br><span class="line">...</span><br><span class="line">(gdb) core-file core</span><br><span class="line">...</span><br><span class="line">(gdb) bt </span><br><span class="line">...</span><br><span class="line"></span><br><span class="line">or</span><br><span class="line"></span><br><span class="line">$ gdb a.out core</span><br><span class="line">...</span><br><span class="line">(gdb) bt </span><br><span class="line">...</span><br></pre></td></tr></table></figure><blockquote><p><strong>编译可执行程序时需要带上<code>-g</code>选项。</strong></p></blockquote><p>如需要在<code>PC</code>上调试嵌入式设备产生的<code>core</code>文件，则需要选取相应平台的<code>gdb</code>工具，并在进入<code>gdb</code>后设置符号文件的位置：</p><figure class="highlight plaintext"><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">$ xxx-xxx-gdb a.out</span><br><span class="line">...</span><br><span class="line">(gdb) solib-search-path xxx.so:xxx.so</span><br><span class="line">...</span><br><span class="line">(gdb) core-file core</span><br><span class="line">...</span><br><span class="line">(gdb) bt</span><br><span class="line">...</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">&lt;p&gt;本文对作者&lt;code&gt;2018&lt;/code&gt;年的博文&lt;a href=&quot;https://answerywj.com/2018/03/07/usage-of-core-in-linux/&quot;&gt;Linux下core文件使用&lt;/a&gt;做了一系列更新，特别是针对&lt;code&gt;Ubuntu 20.04&lt;/code&gt;下&lt;code&gt;core&lt;/code&gt;文件生成异常做了分析与解决。&lt;/p&gt;</summary>
    
    
    
    <category term="C/C++" scheme="https://answerywj.com/categories/C-C/"/>
    
    
    <category term="core" scheme="https://answerywj.com/tags/core/"/>
    
    <category term="core dumped" scheme="https://answerywj.com/tags/core-dumped/"/>
    
    <category term="gdb" scheme="https://answerywj.com/tags/gdb/"/>
    
  </entry>
  
  <entry>
    <title>wav文件格式解析</title>
    <link href="https://answerywj.com/2022/01/06/wav/"/>
    <id>https://answerywj.com/2022/01/06/wav/</id>
    <published>2022-01-06T02:52:53.000Z</published>
    <updated>2023-03-20T06:26:35.342Z</updated>
    
    <content type="html"><![CDATA[<p>本文将详细分析不同格式的<code>wav</code>文件。</p><span id="more"></span><h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p><code>wav</code>即<code>wave</code>（下文统称为<code>wav</code>）, 该文件格式是由微软开发的用于音频数字存储的标准，可应用于<code>Windows</code>、<code>Linux</code>和<code>MacOS</code>等多种操作系统，文件扩展名为<code>.wav</code>，是<code>waveform</code>的简写。</p><h2 id="wav文件结构"><a href="#wav文件结构" class="headerlink" title="wav文件结构"></a>wav文件结构</h2><p>在<code>Windows</code>下，大部分多媒体文件都遵循<code>RIFF</code>（<code>Resource Interchange File Format</code>，资源互换文件格式）格式来存储数据。</p><p><code>RIFF</code>文件的基本存储单位称为块（<code>chunk</code>），一个遵循<code>RIFF</code>格式的文件由若干个<code>chunk</code>组成，每个<code>chunk</code>又由块标识、块长度和块数据三部分构成，其基本结构见<strong>表1</strong>：</p><table><thead><tr><th>Field</th><th>Size(bytes)</th><th>Type</th></tr></thead><tbody><tr><td>块标识</td><td>4</td><td>S8 *</td></tr><tr><td>块长度</td><td>4</td><td>U32</td></tr><tr><td>块数据</td><td></td><td>S8 *</td></tr></tbody></table><p align="center">**表1** `chunk`基本结构</p><ul><li>块标识：用于标识块中的数据，由<code>4</code>个<code>ASCII</code>字符组成，如不满<code>4</code>个字符则在右边以空格充填，如：<code>RIFF</code>、<code>LIST</code>、<code>fmt </code>和<code>data</code>等；</li><li>块长度：用于标识块数据域中的数据长度，块标识域和块长度域不包括在其中，所以一个<code>chunk</code>的实际长度为该值加<code>8</code>;</li><li>块数据：存储块数据，若数据长度为奇数，则在最后添加一个<code>NULL</code>；</li></ul><p>特别需要注意的是，<code>RIFF</code>格式规定只有块标识为<code>RIFF</code>或<code>LIST</code>的块可以含有子块（<code>subChunk</code>），而其它块只可以包含数据。块标识为<code>RIFF</code>或<code>LIST</code>的块的结构与<strong>表1</strong>大致相同，只不过其块数据域分为两部分：</p><ul><li><code>type</code>：由<code>4</code>个<code>ASCII</code>字符组成，代表<code>RIFF</code>文件的类型，如<code>WAVE</code>和<code>AVI </code>，或者代表<code>LIST</code>块的类型，如<code>avi</code>文件中的列表<code>hdrl</code>和<code>movi</code>；</li><li><code>data</code>：实际的块内容，包含若干<code>subChunk</code>；</li></ul><p><code>wav</code>文件是非常简单的一种<code>RIFF</code>文件，其本身就是由一个块标识为<code>RIFF</code>的<code>chunk</code>和多个<code>subChunk</code>组成的一个<code>chunk</code>，其组成如下：</p><ul><li><code>RIFF chunk</code>：主块，必选，块标识为<code>RIFF</code>，说明这是一个<code>RIFF</code>文件，<code>RIFF</code>文件的第一个块标识必须是<code>RIFF</code>；</li><li><code>Format chunk</code>：子块，必选，块标识为<code>fmt </code>，用于存储文件的一些参数信息，比如采样率、通道数和编码格式等；</li><li><code>Fact chunk</code>：子块，可选，块标识为<code>fact</code>，基于压缩编码的<code>wav</code>文件必须含有<code>fact</code>块；</li><li><code>List chunk</code>：子块，可选，块标识为<code>LIST</code>，用于记录文件版权和创建时间信息；</li><li><code>Data chunk</code>：子块，必选，块标识为<code>data</code>，用于存储音频数据；</li></ul><p>目前业界标准的<code>wav</code>文件仅由<code>RIFF chunk</code>、<code>Format chunk</code>和<code>Data chunk</code>组成：<br><img src="/2022/01/06/wav/canonical_wave_file_format.png" alt="标准wav文件格式"></p><h2 id="wav文件头格式"><a href="#wav文件头格式" class="headerlink" title="wav文件头格式"></a>wav文件头格式</h2><p><code>wav</code>文件从数据类型上看，主要由文件头和数据体两部分组成：</p><ul><li>文件头：由<code>RIFF chunk</code>、<code>Format chunk</code>、<code>List chunk</code>和<code>Fact chunk</code>等组成，用于存储一些文件信息；</li><li>数据体：由<code>Data chunk</code>组成，用于存储音频数据；</li></ul><h3 id="标准格式"><a href="#标准格式" class="headerlink" title="标准格式"></a>标准格式</h3><p>标准的<code>wav</code>文件头仅由<code>RIFF chunk</code>和<code>Format chunk</code>组成，长度为<code>44</code>个字节，格式见<strong>表2</strong>：</p><table><thead><tr><th>file offset(bytes)</th><th>Field name</th><th>Field size(bytes)</th><th>type</th><th>endian</th><th>description</th></tr></thead><tbody><tr><td>0</td><td>Chunk ID</td><td>4</td><td>S8 *</td><td>big</td><td>“RIFF”，表明为RIFF文件</td></tr><tr><td>4</td><td>Chunk Size</td><td>4</td><td>U32</td><td>little</td><td>除了RIFF及自己之外，整个文件的长度，即文件总字节数减去8字节</td></tr><tr><td>8</td><td>Format</td><td>4</td><td>S8 *</td><td>big</td><td>“WAVE”，表明为wav格式</td></tr><tr><td>12</td><td>Subchunk1 ID</td><td>4</td><td>S8 *</td><td>big</td><td>“fmt “</td></tr><tr><td>16</td><td>Subchunk1 Size</td><td>4</td><td>U32</td><td>little</td><td>表示fmt数据块即subchunk1除了Subchunk1 ID和Subchunk1 Size之后剩下的长度，一般为16, 大于16表示存在扩展区域，可选值为16、18、20、40等</td></tr><tr><td>20</td><td>AudioFormat</td><td>2</td><td>U16</td><td>little</td><td>编码格式，即压缩格式，0x01表示pcm格式，无压缩，参见<strong>表3</strong></td></tr><tr><td>22</td><td>NumChannels</td><td>2</td><td>U16</td><td>little</td><td>音频通道数，单声道为1，立体声或双声道为2</td></tr><tr><td>24</td><td>SampleRate</td><td>4</td><td>U32</td><td>little</td><td>采样频率，每个通道单位时间采样次数，可选值为16000kHz和44100kHz等</td></tr><tr><td>28</td><td>ByteRate</td><td>4</td><td>U32</td><td>little</td><td>数据传输速率，可用此估算缓冲区长度，ByteRate &#x3D; SampleRate * NumChannels * (BitsPerSample &#x2F; 8)</td></tr><tr><td>32</td><td>BlockAlign</td><td>2</td><td>U16</td><td>little</td><td>采样一次的字节数，即一帧的字节数，表示块对齐的内容（数据块的调整数），播放软件一次处理多少个该长度的字节数据，以便将其用于缓冲区的调整，BlockAlign &#x3D; NumChannels * (BitsPerSample &#x2F; 8)</td></tr><tr><td>34</td><td>BitsPerSample</td><td>2</td><td>U16</td><td>little</td><td>采样位宽，即每个采样点的bit数，可选值8、16或32等</td></tr><tr><td>36</td><td>Subchunk2 ID</td><td>4</td><td>S8 *</td><td>big</td><td>“data”</td></tr><tr><td>40</td><td>Subchunk2 Size</td><td>4</td><td>U32</td><td>little</td><td>音频数据的总长度，即文件总字节数减去wav文件头的长度</td></tr><tr><td>44</td><td>Data</td><td></td><td></td><td>little</td><td>音频数据</td></tr></tbody></table><p align="center">**表2** 标准的`wav`文件头格式</p><p>通过<code>wav</code>文件头信息，我们可以计算出音频时长:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">音频时长(s) = Subchunk2 Size / ByteRate</span><br></pre></td></tr></table></figure><h4 id="压缩编码格式"><a href="#压缩编码格式" class="headerlink" title="压缩编码格式"></a>压缩编码格式</h4><p><code>wav</code>文件几乎支持所有<code>ACM</code>规范的编码格式，其信息存储在<code>wav</code>文件头偏移<code>20</code>、<code>21</code>两个字节中，常见的压缩编码格式见<strong>表3</strong>：</p><table><thead><tr><th>格式代码</th><th>格式名称</th><th>Format chunk 长度</th><th>是否有Fact chunk</th></tr></thead><tbody><tr><td>0 (0x0000)</td><td>unknown</td><td>unknown</td><td>unknown</td></tr><tr><td>1 (0x0001)</td><td>PCM&#x2F;uncompressed</td><td>16</td><td>无</td></tr><tr><td>2 (0x0002)</td><td>Microsoft ADPCM</td><td>18</td><td>有</td></tr><tr><td>3(0x0003)</td><td>IEEE float</td><td>18</td><td>有</td></tr><tr><td>6 (0x0006)</td><td>ITU G.711 a-law</td><td>18</td><td>有</td></tr><tr><td>7 (0x0007)</td><td>ITU G.711 µ-law</td><td>18</td><td>有</td></tr><tr><td>17 (0x0011)</td><td>IMA ADPCM</td><td>unknown</td><td>unknown</td></tr><tr><td>20 (0x0016)</td><td>ITU G.723 ADPCM (Yamaha)</td><td>unknown</td><td>unknown</td></tr><tr><td>49 (0x0031)</td><td>GSM 6.10</td><td>20</td><td>有</td></tr><tr><td>64 (0x0040)</td><td>ITU G.721 ADPCM</td><td>unknown</td><td>有</td></tr><tr><td>80 (0x0050)</td><td>MPEG</td><td>unknown</td><td>unknown</td></tr><tr><td>65,534 (0xFFFE)</td><td>扩展格式标识</td><td>40</td><td>unknown</td></tr><tr><td>65,536 (0xFFFF)</td><td>Experimental</td><td>unknown</td><td>unknown</td></tr></tbody></table><p align="center">**表3** 常见的压缩编码格式</p><h3 id="扩展格式"><a href="#扩展格式" class="headerlink" title="扩展格式"></a>扩展格式</h3><p>当然，也不是所有的<code>wav</code>文件头都是<code>44</code>个字节的，比如通过<code>FFmpge</code>编码而来的<code>wav</code>文件头通常大于<code>44</code>个字节，目前比较常见的<code>wav</code>文件头长度有44字节、46字节、58字节和98字节。</p><h4 id="Format-chunk扩展"><a href="#Format-chunk扩展" class="headerlink" title="Format chunk扩展"></a>Format chunk扩展</h4><p>当<code>wav</code>文件采用非<code>PCM</code>编码即压缩格式时，会扩展<code>Format chunk</code>，在其之后扩充了一个数据结构，见<strong>表4</strong>：</p><table><thead><tr><th>file offset(bytes)</th><th>Field name</th><th>Field size(bytes)</th><th>type</th><th>description</th></tr></thead><tbody><tr><td>24</td><td>extand size</td><td>2</td><td>U16</td><td>除其自身外的扩展区域长度</td></tr><tr><td>26</td><td>extand area</td><td></td><td></td><td>包含扩展的格式信息，其长度取决于压缩编码类型。当某种编码格式（如ITU G.711 a-law）使扩展区的长度为0时，该字段还必须保留，只是长度字段的数值为0。</td></tr></tbody></table><p align="center">**表4** 标准Format chunk扩展格式</p><blockquote><p>由此可以得出，如果<code>Subchunk1 Size</code>等于<code>0x10(16)</code>，表示不包含<code>Format chunk</code>扩展，<code>wav</code>文件头长度为<code>44</code>字节；如果大于<code>0x10(16)</code>，则包含<code>Format chunk</code>扩展，扩展长度的最小值为<code>18(16+2)</code>，此时<code>wav</code>文件头大于<code>44</code>字节。 </p></blockquote><p>当编码格式代码为<code>0xFFFE</code>时，<code>Format chunk</code>扩展长度为<code>24</code>字节，格式见<strong>表5</strong>。</p><table><thead><tr><th>file offset(bytes)</th><th>Field name</th><th>Field size(bytes)</th><th>type</th><th>description</th></tr></thead><tbody><tr><td>24</td><td>扩展区长度</td><td>2</td><td>U16</td><td>值为22</td></tr><tr><td>26</td><td>有效采样位数</td><td>2</td><td>U16</td><td>最大值为 每个采样字节数 * 8</td></tr><tr><td>28</td><td>扬声器位置</td><td>4</td><td>U32</td><td>声道号与扬声器位置映射的二进制掩码</td></tr><tr><td>32</td><td>编码格式</td><td>2</td><td>U16</td><td>真正的编码格式代码</td></tr><tr><td>34</td><td></td><td>14</td><td></td><td>值为{\x00, \x00, \x00, \x00, \x10, \x00, \x80, \x00, \x00, \xAA, \x00, \x38, \x9B, \x71}</td></tr></tbody></table><p align="center">**表5** 编码为0xFFFE的Format chunk扩展格式</p><h4 id="Fact-chunk"><a href="#Fact-chunk" class="headerlink" title="Fact chunk"></a>Fact chunk</h4><p>采用压缩编码（修订版<code>Rev.3</code>以后出现的编码格式）的<code>wav</code>文件必定有含有<code>Fact chunk</code>，其结构符合标准<code>chunk</code>结构，参见<strong>表1</strong>。</p><p><code>Fact chunk</code>的块标识符为”<code>fact</code>“，块长度至少为<code>4</code>个字节，目前其只有一个块数据内容，即每个声道采样总数，或采样帧总数。该值等于<code>Subchunk2 Size / BlockAlign</code>。</p><p>值得注意的是，在实测中发现，将压缩编码格式文件转换成<code>PCM</code>编码格式后，原<code>Fact chunk</code>仍然存在。</p><h2 id="wav文件动态解析库"><a href="#wav文件动态解析库" class="headerlink" title="wav文件动态解析库"></a>wav文件动态解析库</h2><p><code>wav</code>文件格式是一种极其简单的文件格式，如果对其结构足够熟悉，完全可以通过代码正确读写，从而免去引入一些复杂的中间库，降低复杂度，提高工作效率。</p><p>这里提供了一个<a href="https://github.com/AnSwErYWJ/wavfile"><strong>wav文件动态解析库</strong></a>，欢迎使用。</p><h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><ul><li><a href="http://soundfile.sapp.org/doc/WaveFormat/">WAVE PCM soundfile format</a></li><li><a href="https://www.cnblogs.com/qtlx/p/13289462.html">wav音频文件头动态解析–java语言</a></li><li><a href="https://juejin.cn/post/6844904051964903431">史上全最的WAV格式详解</a></li><li><a href="https://www.cnblogs.com/ranson7zop/p/7657874.html">wav文件格式分析与详解</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;p&gt;本文将详细分析不同格式的&lt;code&gt;wav&lt;/code&gt;文件。&lt;/p&gt;</summary>
    
    
    
    <category term="语音" scheme="https://answerywj.com/categories/%E8%AF%AD%E9%9F%B3/"/>
    
    
    <category term="wav" scheme="https://answerywj.com/tags/wav/"/>
    
    <category term="header" scheme="https://answerywj.com/tags/header/"/>
    
  </entry>
  
  <entry>
    <title>inline使用注意事项</title>
    <link href="https://answerywj.com/2021/07/28/tips-of-inline/"/>
    <id>https://answerywj.com/2021/07/28/tips-of-inline/</id>
    <published>2021-07-28T06:34:31.000Z</published>
    <updated>2023-03-20T06:26:35.342Z</updated>
    
    <content type="html"><![CDATA[<p><strong><code>GCC</code>在不优化时不会内联任何函数，除非指定函数的“<code>always_inline</code>”属性。</strong></p><span id="more"></span><p>先附上结论：**<code>GCC</code>在不优化时不会内联任何函数，除非指定函数的“<code>always_inline</code>”属性。**<br>​</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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;stdio.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">inline</span> <span class="type">void</span> <span class="title function_">say</span><span class="params">(<span class="type">void</span>)</span> &#123;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;Hello, World\n&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">(<span class="type">void</span>)</span> &#123;</span><br><span class="line">    say();</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>使用<code>-O3</code>优化选项，一切正常：</p><figure class="highlight bash"><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 -O3 -o test_O3.o -c test.c</span><br><span class="line">$ g++ test_O3.o -o test_O3</span><br><span class="line">$ ./test_O3</span><br><span class="line">Hello, World</span><br></pre></td></tr></table></figure><p>使用<code>-O0</code>优化选项，链接时报错，提示找不到内联函数<code>say</code>：</p><figure class="highlight bash"><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 -O0 -o test_O0.o -c test.c</span><br><span class="line">$ g++ test_O0.o -o test_O0</span><br><span class="line">test_O0.o: In <span class="keyword">function</span> `main<span class="string">&#x27;:</span></span><br><span class="line"><span class="string">test.c:(.text+0x5): undefined reference to `say&#x27;</span></span><br><span class="line">collect2: error: ld returned 1 <span class="built_in">exit</span> status</span><br></pre></td></tr></table></figure><p>分别查看文件<code>test_O0.o</code>和<code>test_O3.o</code>：</p><figure class="highlight bash"><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">$ readelf -s test_O0.o</span><br><span class="line"></span><br><span class="line">Symbol table <span class="string">&#x27;.symtab&#x27;</span> contains 10 entries:</span><br><span class="line">   Num:    Value          Size Type    Bind   Vis      Ndx Name</span><br><span class="line">     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND </span><br><span class="line">     1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS test.c</span><br><span class="line">     2: 0000000000000000     0 SECTION LOCAL  DEFAULT    1 </span><br><span class="line">     3: 0000000000000000     0 SECTION LOCAL  DEFAULT    3 </span><br><span class="line">     4: 0000000000000000     0 SECTION LOCAL  DEFAULT    4 </span><br><span class="line">     5: 0000000000000000     0 SECTION LOCAL  DEFAULT    6 </span><br><span class="line">     6: 0000000000000000     0 SECTION LOCAL  DEFAULT    7 </span><br><span class="line">     7: 0000000000000000     0 SECTION LOCAL  DEFAULT    5 </span><br><span class="line">     8: 0000000000000000    16 FUNC    GLOBAL DEFAULT    1 main</span><br><span class="line">     9: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND say</span><br></pre></td></tr></table></figure><figure class="highlight bash"><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">$ readelf -s test_O3.o</span><br><span class="line"></span><br><span class="line">Symbol table <span class="string">&#x27;.symtab&#x27;</span> contains 13 entries:</span><br><span class="line">   Num:    Value          Size Type    Bind   Vis      Ndx Name</span><br><span class="line">     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND </span><br><span class="line">     1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS test.c</span><br><span class="line">     2: 0000000000000000     0 SECTION LOCAL  DEFAULT    1 </span><br><span class="line">     3: 0000000000000000     0 SECTION LOCAL  DEFAULT    2 </span><br><span class="line">     4: 0000000000000000     0 SECTION LOCAL  DEFAULT    3 </span><br><span class="line">     5: 0000000000000000     0 SECTION LOCAL  DEFAULT    4 </span><br><span class="line">     6: 0000000000000000     0 SECTION LOCAL  DEFAULT    5 </span><br><span class="line">     7: 0000000000000000     0 SECTION LOCAL  DEFAULT    6 </span><br><span class="line">     8: 0000000000000000     0 SECTION LOCAL  DEFAULT    9 </span><br><span class="line">     9: 0000000000000000     0 SECTION LOCAL  DEFAULT   10 </span><br><span class="line">    10: 0000000000000000     0 SECTION LOCAL  DEFAULT    8 </span><br><span class="line">    11: 0000000000000000    21 FUNC    GLOBAL DEFAULT    6 main</span><br><span class="line">    12: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND puts</span><br></pre></td></tr></table></figure><p>可以发现文件<code>test_O0.o</code>中，<code>inline</code>修饰的函数<code>say</code>为未定义状态，说明<code>inline</code>函数并没有展开。<br>​</p><p>指定<code>say</code>函数为“<code>always_inline</code>”属性：</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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;stdio.h&gt;</span></span></span><br><span class="line"></span><br><span class="line">__attribute__((always_inline)) <span class="keyword">inline</span> <span class="type">void</span> <span class="title function_">say</span><span class="params">(<span class="type">void</span>)</span> &#123;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;Hello, World\n&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">(<span class="type">void</span>)</span> &#123;</span><br><span class="line">    say();</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>重新使用<code>-O0</code>优化选项编译运行，一切<code>ok</code>：</p><figure class="highlight bash"><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 -O0 -o test.o -c test.c</span><br><span class="line">$ gcc -O0 -o test_O0_2.o -c test.c</span><br><span class="line">$ g++ test_O0_2.o -o test_O0_2</span><br><span class="line">$ ./test_O0_2</span><br><span class="line">Hello, World</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">&lt;p&gt;&lt;strong&gt;&lt;code&gt;GCC&lt;/code&gt;在不优化时不会内联任何函数，除非指定函数的“&lt;code&gt;always_inline&lt;/code&gt;”属性。&lt;/strong&gt;&lt;/p&gt;</summary>
    
    
    
    <category term="C/C++" scheme="https://answerywj.com/categories/C-C/"/>
    
    
    <category term="inline" scheme="https://answerywj.com/tags/inline/"/>
    
    <category term="O0" scheme="https://answerywj.com/tags/O0/"/>
    
  </entry>
  
  <entry>
    <title>getrusage-进程资源统计函数</title>
    <link href="https://answerywj.com/2021/06/23/getrusage/"/>
    <id>https://answerywj.com/2021/06/23/getrusage/</id>
    <published>2021-06-23T07:41:07.000Z</published>
    <updated>2023-03-20T06:26:35.318Z</updated>
    
    <content type="html"><![CDATA[<p><code>getrusage</code>用于统计系统资源使用情况，即进程执行直到调用该函数时的资源使用情况，如果在不同的时间调用该函数，会得到不同的结果。</p><span id="more"></span><h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p><code>getrusage</code>用于统计系统资源使用情况，即进程执行直到调用该函数时的资源使用情况，如果在不同的时间调用该函数，会得到不同的结果。</p><blockquote><p>目前在<code>Linux</code>和<code>macOS</code>支持该函数。</p></blockquote><h2 id="函数说明"><a href="#函数说明" class="headerlink" title="函数说明"></a>函数说明</h2><h3 id="原型"><a href="#原型" class="headerlink" title="原型"></a>原型</h3><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="meta">#<span class="keyword">include</span> <span class="string">&lt;sys/time.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;sys/resource.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">getrusage</span><span class="params">(<span class="type">int</span> who, <span class="keyword">struct</span> rusage *usage)</span>;</span><br></pre></td></tr></table></figure><blockquote><p><code>sys/time.h</code>：为了得到<code>timeval</code>结构体的声明，这个结构体实际上在<code>bits/time.h</code>中声明。</p></blockquote><h3 id="参数"><a href="#参数" class="headerlink" title="参数"></a>参数</h3><p>**<code>who</code>**：资源统计的对象，有如下取值：</p><ul><li><code>RUSAGE_SELF</code>：返回调用进程的资源使用统计信息，即该进程中所有线程使用的资源总和；</li><li><code>RUSAGE_CHILDREN</code>：返回调用进程所有已终止且被回收子进程的资源使用统计信息。如果进程有孙子进程或更远的后代进程，且这些后代进程和这些后代进程与调用进程之间的中间进程也已终止且被回收，那么这些后代进程的资源使用统计信息也会被统计；</li><li><code>RUSAGE_THREAD</code>（<code>Linux 2.6.26</code>起支持）：返回调用线程的资源使用统计信息；</li></ul><table><thead><tr><th>宏定义</th><th>取值</th></tr></thead><tbody><tr><td>RUSAGE_SELF</td><td>0</td></tr><tr><td>RUSAGE_CHILDREN</td><td>-1</td></tr><tr><td>RUSAGE_THREAD</td><td>1</td></tr></tbody></table><blockquote><p>宏定义在<code>sys/resource.h</code>-&gt; <code>bits/resource.h</code>。</p></blockquote><p>**<code>usage</code>**：资源使用统计信息，用如下结构体的形式返回到该指针指向的内存空间：</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></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">rusage</span> &#123;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">timeval</span> <span class="title">ru_utime</span>;</span> <span class="comment">/* user CPU time used */</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">timeval</span> <span class="title">ru_stime</span>;</span> <span class="comment">/* system CPU time used */</span></span><br><span class="line">    <span class="type">long</span>   ru_maxrss;        <span class="comment">/* maximum resident set size */</span></span><br><span class="line">    <span class="type">long</span>   ru_ixrss;         <span class="comment">/* integral shared memory size */</span></span><br><span class="line">    <span class="type">long</span>   ru_idrss;         <span class="comment">/* integral unshared data size */</span></span><br><span class="line">    <span class="type">long</span>   ru_isrss;         <span class="comment">/* integral unshared stack size */</span></span><br><span class="line">    <span class="type">long</span>   ru_minflt;        <span class="comment">/* page reclaims (soft page faults) */</span></span><br><span class="line">    <span class="type">long</span>   ru_majflt;        <span class="comment">/* page faults (hard page faults) */</span></span><br><span class="line">    <span class="type">long</span>   ru_nswap;         <span class="comment">/* swaps */</span></span><br><span class="line">    <span class="type">long</span>   ru_inblock;       <span class="comment">/* block input operations */</span></span><br><span class="line">    <span class="type">long</span>   ru_oublock;       <span class="comment">/* block output operations */</span></span><br><span class="line">    <span class="type">long</span>   ru_msgsnd;        <span class="comment">/* IPC messages sent */</span></span><br><span class="line">    <span class="type">long</span>   ru_msgrcv;        <span class="comment">/* IPC messages received */</span></span><br><span class="line">    <span class="type">long</span>   ru_nsignals;      <span class="comment">/* signals received */</span></span><br><span class="line">    <span class="type">long</span>   ru_nvcsw;         <span class="comment">/* voluntary context switches */</span></span><br><span class="line">    <span class="type">long</span>   ru_nivcsw;        <span class="comment">/* involuntary context switches */</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>结构体<code>struct rusage</code>各个成员释义如下：</p><ul><li><code>ru_utime</code>：返回进程在用户模式下的执行时间，以<code>timeval</code>结构的形式返回（该结构体在<code>bits/timeval</code>中声明）；</li><li><code>ru_stime</code>：返回进程在内核模式下的执行时间，以<code>timeval</code>结构的形式返回（该结构体在<code>bits/timeval</code>中声明）；</li><li><code>ru_maxrss</code>（<code>Linux 2.6.32</code>起支持）：返回<code>rss</code>（实际使用物理内存，包含共享库占用的内存）的大小，单位为<code>KB</code>；当<code>who</code>被指定为<code>RUSAGE_CHILDREN</code>时，返回各子进程<code>rss</code>的大小中最大的一个，而不是进程树中最大的<code>rss</code>；</li><li><code>ru_ixrss</code>：目前不支持；</li><li><code>ru_idrss</code>：目前不支持；</li><li><code>ru_isrss</code>：目前不支持；</li><li><code>ru_minflt</code>：缺页中断的次数，且处理这些中断不需要进行<code>I/O</code>，不需要进行<code>I/O</code>操作的原因是系统使用<code>reclaiming</code>的方式在物理内存中得到了之前被淘汰但是未被修改的页框。（第一次访问<code>bss</code>段时也会产生这种类型的缺页中断）；</li><li><code>ru_majflt</code>：缺页中断的次数，且处理这些中断需要进行<code>I/O</code>；</li><li><code>ru_nswap</code>：目前不支持；</li><li><code>ru_inblock</code>（<code>Linux 2.6.22</code>起支持）：文件系统需要进行输入操作的次数；</li><li><code>ru_oublock</code>（<code>Linux 2.6.22</code>起支持）：文件系统需要进行输出操作的次数；</li><li><code>ru_msgsnd</code>：目前不支持；</li><li><code>ru_msgrcv</code>：目前不支持；</li><li><code>ru_nsignals</code>：目前不支持；</li><li><code>ru_nvcsw</code>（<code>Linux 2.6</code>起支持）：因进程自愿放弃处理器时间片而导致的上下文切换的次数（通常是为了等待请求的资源）；</li><li><code>ru_nivcsw</code>（<code>Linux 2.6</code>起支持）：因进程时间片使用完毕或被高优先级进程抢断导致的上下文切换的次数；<blockquote><p>其中有些结构体成员目前并不被<code>Linxu</code>支持，但是为了兼容其它系统以及未来扩展，仍被保留了下来，这些结构体成员在函数执行后会被内核默认设置为<code>0</code>。</p></blockquote></li></ul><h3 id="返回值"><a href="#返回值" class="headerlink" title="返回值"></a>返回值</h3><p><strong>成功</strong>：<code>0</code>；<br><strong>失败</strong>：<code>-1</code>，并设置<code>errno</code>的值，包含如下两种错误：</p><ul><li><code>EFAULT</code>：<code>usage</code>指针指向不可访问地址；</li><li><code>EINVAL</code>：<code>who</code>被指定为无效值；</li></ul><h3 id="属性"><a href="#属性" class="headerlink" title="属性"></a>属性</h3><p><code>getrusage</code>函数是线程安全的。<br>​</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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;stdio.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* include for getrusage */</span></span><br><span class="line"><span class="meta">#<span class="keyword">ifndef</span> _WIN32</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;sys/time.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;sys/resource.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">static</span> <span class="type">void</span> <span class="title function_">print_rusage</span><span class="params">()</span> &#123;</span><br><span class="line"><span class="meta">#<span class="keyword">ifndef</span> _WIN32</span></span><br><span class="line"><span class="type">int</span> ret;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">rusage</span> <span class="title">usage</span>;</span></span><br><span class="line">    ret = getrusage(RUSAGE_SELF, &amp;usage);</span><br><span class="line"><span class="keyword">if</span> (<span class="number">0</span> != ret) &#123;</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">&quot;getrusage failed\n&quot;</span>);</span><br><span class="line"><span class="keyword">goto</span> end;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="built_in">printf</span>(<span class="string">&quot;%s: %.3fms\n&quot;</span>, <span class="string">&quot;ru_utime&quot;</span>, (usage.ru_utime.tv_sec * <span class="number">1000.0</span> + usage.ru_utime.tv_usec / <span class="number">1000.0</span>));</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">&quot;%s: %.3fms\n&quot;</span>, <span class="string">&quot;ru_stime&quot;</span>, (usage.ru_stime.tv_sec * <span class="number">1000.0</span> + usage.ru_stime.tv_usec / <span class="number">1000.0</span>));</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">&quot;%s: %.3fM\n&quot;</span>, <span class="string">&quot;ru_maxrss&quot;</span>, (usage.ru_maxrss / <span class="number">1024.0</span>));</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">&quot;%s: %ld\n&quot;</span>, <span class="string">&quot;ru_ixrss&quot;</span>, usage.ru_ixrss);</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">&quot;%s: %ld\n&quot;</span>, <span class="string">&quot;ru_idrss&quot;</span>, usage.ru_idrss);</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">&quot;%s: %ld\n&quot;</span>, <span class="string">&quot;ru_isrss&quot;</span>, usage.ru_isrss);</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">&quot;%s: %ld\n&quot;</span>, <span class="string">&quot;ru_minflt&quot;</span>, usage.ru_minflt);</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">&quot;%s: %ld\n&quot;</span>, <span class="string">&quot;ru_majflt&quot;</span>, usage.ru_majflt);</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">&quot;%s: %ld\n&quot;</span>, <span class="string">&quot;ru_nswap&quot;</span>, usage.ru_nswap);</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">&quot;%s: %ld\n&quot;</span>, <span class="string">&quot;ru_inblock&quot;</span>, usage.ru_inblock);</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">&quot;%s: %ld\n&quot;</span>, <span class="string">&quot;ru_oublock&quot;</span>, usage.ru_oublock);</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">&quot;%s: %ld\n&quot;</span>, <span class="string">&quot;ru_msgsnd&quot;</span>, usage.ru_msgsnd);</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">&quot;%s: %ld\n&quot;</span>, <span class="string">&quot;ru_msgrcv&quot;</span>, usage.ru_msgrcv);</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">&quot;%s: %ld\n&quot;</span>, <span class="string">&quot;ru_nsignals&quot;</span>, usage.ru_nsignals);</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">&quot;%s: %ld\n&quot;</span>, <span class="string">&quot;ru_nvcsw&quot;</span>, usage.ru_nvcsw);</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">&quot;%s: %ld\n&quot;</span>, <span class="string">&quot;ru_nivcsw&quot;</span>, usage.ru_nivcsw);</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br><span class="line"></span><br><span class="line">end:</span><br><span class="line"><span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>完整代码：<a href="https://github.com/AnSwErYWJ/DogFood/blob/master/C/getrusage.c">https://github.com/AnSwErYWJ/DogFood/blob/master/C/getrusage.c</a>。</p></blockquote>]]></content>
    
    
    <summary type="html">&lt;p&gt;&lt;code&gt;getrusage&lt;/code&gt;用于统计系统资源使用情况，即进程执行直到调用该函数时的资源使用情况，如果在不同的时间调用该函数，会得到不同的结果。&lt;/p&gt;</summary>
    
    
    
    <category term="C/C++" scheme="https://answerywj.com/categories/C-C/"/>
    
    
    <category term="getrusage" scheme="https://answerywj.com/tags/getrusage/"/>
    
  </entry>
  
  <entry>
    <title>RPATH与RUNPATH的区别</title>
    <link href="https://answerywj.com/2021/02/22/difference-between-RPATH-and-RUNPATH/"/>
    <id>https://answerywj.com/2021/02/22/difference-between-RPATH-and-RUNPATH/</id>
    <published>2021-02-22T10:26:04.000Z</published>
    <updated>2023-03-20T06:26:35.318Z</updated>
    
    <content type="html"><![CDATA[<p>本文从一个实际遇到的问题出发，分析 <code>RPATH</code> 与 <code>RUNPATH</code> 的区别，以及产生的原因。</p><span id="more"></span><h1 id="RPATH与RUNPATH的区别"><a href="#RPATH与RUNPATH的区别" class="headerlink" title="RPATH与RUNPATH的区别"></a>RPATH与RUNPATH的区别</h1><p>年前升级了操作系统后，同样的代码在新系统编译后无法执行，提示找不到依赖库，本文用来记录一下是如何解决这个问题的。</p><h2 id="源文件"><a href="#源文件" class="headerlink" title="源文件"></a>源文件</h2><p><code>main.c</code> ：</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="keyword">include</span> <span class="string">&quot;a.h&quot;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">()</span> &#123;</span><br><span class="line">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><code>libA.so</code>：</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="comment">// a.c</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;b.h&quot;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">void</span> <span class="title function_">a</span><span class="params">()</span> &#123;</span><br><span class="line">b();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><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="comment">// a.h</span></span><br><span class="line"><span class="type">void</span> <span class="title function_">a</span><span class="params">()</span>;</span><br></pre></td></tr></table></figure><p><code>ubuntu 16.04(gcc version 5.4.0)</code>：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ gcc -fPIC -shared a.c -I. -L. -lB -o libA.so</span><br></pre></td></tr></table></figure><p><code>libB.so</code>：</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="comment">// b.c</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;stdio.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">void</span> <span class="title function_">b</span><span class="params">()</span> &#123;</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">&quot;Hello, World\n&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><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="comment">// b.h</span></span><br><span class="line"><span class="type">void</span> <span class="title function_">b</span><span class="params">()</span>;</span><br></pre></td></tr></table></figure><p><code>ubuntu 16.04(gcc version 5.4.0)</code>：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ gcc -fPIC -shared -I. b.c -o libB.so</span><br></pre></td></tr></table></figure><blockquote><p>**函数调用依赖关系：<code>main</code> -&gt; <code>a</code> -&gt; <code>b</code>**。</p></blockquote><h2 id="复现步骤"><a href="#复现步骤" class="headerlink" title="复现步骤"></a>复现步骤</h2><ol><li><p>分别在 <code>ubuntu 16.04(gcc version 5.4.0)</code> 和 <code>ubuntu 20.04(gcc version 9.3.0)</code> 编译可执行程序：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ gcc -I. -o main main.c -Wl,--rpath,. -L. -lA -lB</span><br></pre></td></tr></table></figure></li><li><p>在 <code>ubuntu 16.04(gcc version 5.4.0)</code> 运行<strong>成功</strong>：</p><figure class="highlight bash"><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">Hello, World</span><br></pre></td></tr></table></figure></li><li><p>在 <code>ubuntu 20.04(gcc version 9.3.0)</code> 运行<strong>失败</strong>：</p><figure class="highlight bash"><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: libB.so: cannot open shared object file: No such file or directory</span><br></pre></td></tr></table></figure><blockquote><p>错误提示为找不到可执行程序 <code>./main</code> 依赖的共享库 <code>libB.so</code> 。</p></blockquote></li></ol><h2 id="问题原因"><a href="#问题原因" class="headerlink" title="问题原因"></a>问题原因</h2><h3 id="排除共享库本身问题"><a href="#排除共享库本身问题" class="headerlink" title="排除共享库本身问题"></a>排除共享库本身问题</h3><p>首先，由于 <code>libA.so</code> 和 <code>libB.so</code> 是在 <code>ubuntu 16.04(gcc version 5.4.0)</code> 上编译的，所以重新在<code>ubuntu 20.04(gcc version 9.3.0)</code> 上编译 <code>libA.so</code> 和 <code>libB.so</code>，并重复复现步骤，现象相同，说明不是 <code>libA.so</code> 和 <code>libB.so</code> 导致的问题；</p><h3 id="分析库查找过程"><a href="#分析库查找过程" class="headerlink" title="分析库查找过程"></a>分析库查找过程</h3><p>使用 **<code>LD_DEBUG</code>**，打开链接器的调试功能，分析共享库的查找过程：<br><code>ubuntu 16.04(gcc version 5.4.0)</code>：</p><figure class="highlight bash"><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">$ LD_DEBUG=libs ./main                                                        [16:37:15]</span><br><span class="line">     22331:find library=libA.so [0]; searching</span><br><span class="line">     22331: search path=./tls/x86_64:./tls:./x86_64:.(RPATH from file ./main)</span><br><span class="line">......</span><br><span class="line">     22331:  trying file=./libA.so</span><br><span class="line">......</span><br><span class="line">     22331:find library=libB.so [0]; searching</span><br><span class="line">     22331: search path=./tls/x86_64:./tls:./x86_64:.(RPATH from file ./main)</span><br><span class="line">......</span><br><span class="line">     22331:  trying file=./libB.so</span><br><span class="line"> ......</span><br></pre></td></tr></table></figure><p><code>ubuntu 20.04(gcc version 9.3.0)</code>：</p><figure class="highlight bash"><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">$ LD_DEBUG=libs ./main</span><br><span class="line">     33218:find library=libA.so [0]; searching</span><br><span class="line">     33218: search path=./tls/haswell/x86_64:./tls/haswell:./tls/x86_64:./tls:./haswell/x86_64:./haswell:./x86_64:.(RUNPATH from file ./main)</span><br><span class="line">......</span><br><span class="line">     33218:  trying file=./libA.so</span><br><span class="line">......</span><br><span class="line">     33218:find library=libB.so [0]; searching</span><br><span class="line">     33218: search cache=/etc/ld.so.cache</span><br><span class="line">     33218: search path=/lib/x86_64-linux-gnu/tls/haswell/x86_64:/lib/x86_64-linux-gnu/tls/haswell:/lib/x86_64-linux-gnu/tls/x86_64:/lib/x86_64-linux-gnu/tls:/lib/x86_64-linux-gnu/haswell/x86_64:/lib/x86_64-linux-gnu/haswell:/lib/x86_64-linux-gnu/x86_64:/lib/x86_64-linux-gnu:/usr/lib/x86_64-linux-gnu/tls/haswell/x86_64:/usr/lib/x86_64-linux-gnu/tls/haswell:/usr/lib/x86_64-linux-gnu/tls/x86_64:/usr/lib/x86_64-linux-gnu/tls:/usr/lib/x86_64-linux-gnu/haswell/x86_64:/usr/lib/x86_64-linux-gnu/haswell:/usr/lib/x86_64-linux-gnu/x86_64:/usr/lib/x86_64-linux-gnu:/lib/tls/haswell/x86_64:/lib/tls/haswell:/lib/tls/x86_64:/lib/tls:/lib/haswell/x86_64:/lib/haswell:/lib/x86_64:/lib:/usr/lib/tls/haswell/x86_64:/usr/lib/tls/haswell:/usr/lib/tls/x86_64:/usr/lib/tls:/usr/lib/haswell/x86_64:/usr/lib/haswell:/usr/lib/x86_64:/usr/lib(system search path)</span><br><span class="line">......</span><br><span class="line">./main: error <span class="keyword">while</span> loading shared libraries: libB.so: cannot open shared object file: No such file or directory</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>结果在 <code>ubuntu 20.04(gcc version 9.3.0)</code> 中， 可以正确查找到 <code>libA.so</code> ，但是无法正确查找到 <code>libB.so</code> 。<br><code>libB.so</code> 的查找路径是 <code>system search path</code> ，而非我们在编译时设定的查找时路径 <code>./</code> ，导致可执行程序无法加载 <code>libB.so</code> 。</p><blockquote><p>动态链接器对共享库的查找顺序：</p><ol><li><code>LD_LIBRARY_PATH</code>、<code>-L</code> 和 <code>-rpath</code>；</li><li><code>/etc/ld.so.cache</code>；</li><li>默认共享库目录：<code>/usr/lib</code>、<code>/lib</code>；</li></ol></blockquote><h3 id="RPATH与RUNPATH的区别-1"><a href="#RPATH与RUNPATH的区别-1" class="headerlink" title="RPATH与RUNPATH的区别"></a>RPATH与RUNPATH的区别</h3><p>由上分析可以得出是共享库运行时加载路径非法导致的问题，在排除设置 <code>LD_LIBRARY_PATH</code> 等环境变量的情况下，可以将焦点锁定在使用的链接选项 <code>-rpath</code> 上，查看源文件依赖：<br><code>ubuntu 16.04(gcc version 5.4.0)</code>：</p><figure class="highlight bash"><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">$ readelf -d main</span><br><span class="line">Dynamic section at offset 0xe08 contains 26 entries:</span><br><span class="line">  Tag        Type                         Name/Value</span><br><span class="line"> 0x0000000000000001 (NEEDED)             Shared library: [libA.so]</span><br><span class="line"> 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]</span><br><span class="line"> 0x000000000000000f (RPATH)              Library rpath: [.]</span><br><span class="line"> </span><br><span class="line"> $ readelf -d libA.so</span><br><span class="line">Dynamic section at offset 0xe08 contains 25 entries:</span><br><span class="line">  Tag        Type                         Name/Value</span><br><span class="line"> 0x0000000000000001 (NEEDED)             Shared library: [libB.so]</span><br><span class="line"> 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]</span><br><span class="line"> </span><br><span class="line"> $ readelf -d libB.so</span><br><span class="line">Dynamic section at offset 0xe18 contains 24 entries:</span><br><span class="line">  Tag        Type                         Name/Value</span><br><span class="line"> 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]</span><br></pre></td></tr></table></figure><p><code>ubuntu 20.04(gcc version 9.3.0)</code>：</p><figure class="highlight bash"><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">$ readelf -d main </span><br><span class="line">Dynamic section at offset 0x2da8 contains 29 entries:</span><br><span class="line">  Tag        Type                         Name/Value</span><br><span class="line"> 0x0000000000000001 (NEEDED)             Shared library: [libA.so]</span><br><span class="line"> 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]</span><br><span class="line"> 0x000000000000001d (RUNPATH)            Library runpath: [.]</span><br><span class="line"> </span><br><span class="line"> $ readelf -d libA.so </span><br><span class="line">Dynamic section at offset 0xe08 contains 25 entries:</span><br><span class="line">  Tag        Type                         Name/Value</span><br><span class="line"> 0x0000000000000001 (NEEDED)             Shared library: [libB.so]</span><br><span class="line"> 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]</span><br><span class="line"> </span><br><span class="line"> $ readelf -d libB.so </span><br><span class="line">Dynamic section at offset 0xe18 contains 24 entries:</span><br><span class="line">  Tag        Type                         Name/Value</span><br><span class="line"> 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]</span><br></pre></td></tr></table></figure><p>发现在 <code>ubuntu 20.04(gcc version 9.3.0)</code> 上 <code>RPATH</code> 变成了 <code>RUNPATH</code> ，说明链接器选项 <code>-rpath</code> 的行为发生了改变。</p><blockquote><p>源文件依赖关系：<code>main</code> -&gt; <code>libA.so</code> -&gt; <code>libB.so</code>；</p></blockquote><p><strong>综上，问题的原因是 <code>ubuntu 20.04(gcc version 9.3.0) </code>上，链接器选项 <code>-rpath</code> 的行为发生改变，默认配置为 <code>RUNPATH</code> 而不是 <code>RPATH</code>；由于 <code>RUNPATH</code> 不适用于间接依赖的库，所以导致在 <code>ubuntu 20.04(gcc version 9.3.0)</code> 上只能正确查找到 <code>libA.so</code> ，而无法正确查找到 <code>libB.so</code> 。</strong></p><blockquote><p><code>gcc version &gt;= 7.5.0</code>时，<code>-rpath</code>默认行为即发生改变。</p></blockquote><h2 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h2><h3 id="LD-LIBRARY-PATH（不推荐）"><a href="#LD-LIBRARY-PATH（不推荐）" class="headerlink" title="LD_LIBRARY_PATH（不推荐）"></a>LD_LIBRARY_PATH（不推荐）</h3><p><code>LD_LIBRARY_PATH</code> 是一个环境变量，作用是<strong>临时</strong>改变链接器的加载路径，可以存储多个路径，用冒号分隔：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ <span class="built_in">export</span> LD_LIBRARY_PATH=<span class="variable">$LD_LIBRARY_PATH</span>:./libs</span><br></pre></td></tr></table></figure><p>不推荐的原因：</p><ol><li>若全局设置 <code>LD_LIBRARY_PATH</code>，会影响其它应用程序的共享库加载过程；</li><li>若只在该应用程序启动时局部设置 <code>LD_LIBRARY_PATH</code>，则每次启动都需要设置，步骤过于繁琐；</li></ol><h3 id="–disable-new-dtags"><a href="#–disable-new-dtags" class="headerlink" title="–disable-new-dtags"></a>–disable-new-dtags</h3><p>可以使用 <code>-Wl,--disable-new-dtags</code> 选项来使链接器保持旧行为，即在 <code>ubuntu 20.04(gcc version 9.3.0)</code> 使用如下命令编译：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ gcc -I. -o main main.c -Wl,--disable-new-dtags,--rpath,. -L. -lA -lB</span><br></pre></td></tr></table></figure><p>重新运行并查看依赖：</p><figure class="highlight bash"><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">$ ./main </span><br><span class="line">Hello, World</span><br><span class="line"></span><br><span class="line">$ readelf -d main</span><br><span class="line">Dynamic section at offset 0x2da8 contains 29 entries:</span><br><span class="line">  Tag        Type                         Name/Value</span><br><span class="line"> 0x0000000000000001 (NEEDED)             Shared library: [libA.so]</span><br><span class="line"> 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]</span><br><span class="line"> 0x000000000000000f (RPATH)              Library rpath: [.]</span><br></pre></td></tr></table></figure><p>可执行程序 <code>main</code> 可以正确运行，<code>RUNPATH</code> 也变成了 <code>RPATH</code>，链接器行为与 <code>ubuntu 16.04(gcc version 5.4.0)</code> 保持一致了。</p><blockquote><p>同理，也有 <code>-Wl,--enable-new-dtags</code> 选项来使链接器保持新行为</p></blockquote><p>如下为官方解释：</p><figure class="highlight plaintext"><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">--enable-new-dtags</span><br><span class="line">--disable-new-dtags</span><br><span class="line">This linker can create the new dynamic tags in ELF. But the older ELF systems may not understand them. If you specify --enable-new-dtags, the new dynamic tags will be created as needed and older dynamic tags will be omitted. If you specify --disable-new-dtags, no new dynamic tags will be created. By default, the new dynamic tags are not created. Note that those options are only available for ELF systems.</span><br></pre></td></tr></table></figure><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li><a href="https://stackoverflow.com/questions/52018092/how-to-set-rpath-and-runpath-with-gcc-ld">How to set RPATH and RUNPATH with GCC&#x2F;LD?</a></li><li><a href="https://stackoverflow.com/questions/7967848/use-rpath-but-not-runpath">use RPATH but not RUNPATH?</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;p&gt;本文从一个实际遇到的问题出发，分析 &lt;code&gt;RPATH&lt;/code&gt; 与 &lt;code&gt;RUNPATH&lt;/code&gt; 的区别，以及产生的原因。&lt;/p&gt;</summary>
    
    
    
    <category term="编译链接" scheme="https://answerywj.com/categories/%E7%BC%96%E8%AF%91%E9%93%BE%E6%8E%A5/"/>
    
    
    <category term="RPATH" scheme="https://answerywj.com/tags/RPATH/"/>
    
    <category term="RUNPATH" scheme="https://answerywj.com/tags/RUNPATH/"/>
    
    <category term="LD_DEBUG" scheme="https://answerywj.com/tags/LD-DEBUG/"/>
    
  </entry>
  
  <entry>
    <title>Git速查手册（第三版）</title>
    <link href="https://answerywj.com/2020/09/29/git-help-v3/"/>
    <id>https://answerywj.com/2020/09/29/git-help-v3/</id>
    <published>2020-09-29T07:13:42.000Z</published>
    <updated>2023-10-20T09:32:07.456Z</updated>
    
    <content type="html"><![CDATA[<p>本文是对<a href="https://answerywj.com/2019/02/12/git-help-v2/">Git速查手册（第二版）</a>的更新，补充了一些近期使用或者收集的一些命令。</p><span id="more"></span><h2 id="下载与安装"><a href="#下载与安装" class="headerlink" title="下载与安装"></a>下载与安装</h2><p><code>Git</code>下载地址：<a href="https://git-scm.com/downloads">https://git-scm.com/downloads</a>，安装请参考页面说明。</p><blockquote><p>建议使用版本<code>v1.8</code>及以上。</p></blockquote><h2 id="配置"><a href="#配置" class="headerlink" title="配置"></a>配置</h2><p><code>Git</code>配置分为三个级别：</p><ul><li><code>--system</code>：系统级，位于 <code>/etc/gitconfig</code>；</li><li><code>--global</code>：用户级，位于 <code>~/.gitconfig</code>；</li><li><code>--local</code>：仓库级，位于 <code>[repo]/.git/config</code>，为<em>默认级别且优先级最高</em>；</li></ul><h3 id="用户信息"><a href="#用户信息" class="headerlink" title="用户信息"></a>用户信息</h3><p>删除<code>global</code>用户信息，防止不同<code>Git</code>服务之间冲突：</p><figure class="highlight plaintext"><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">$ git config --global --unset user.name</span><br><span class="line">$ git config --global --unset user.email</span><br></pre></td></tr></table></figure><p>配置用户名：</p><figure class="highlight plaintext"><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">$ git config --local user.name &quot;username&quot;</span><br><span class="line">$ git config --local user.email &quot;email&quot;</span><br></pre></td></tr></table></figure><h3 id="克隆协议"><a href="#克隆协议" class="headerlink" title="克隆协议"></a>克隆协议</h3><p>一般<code>Git</code>服务默认都支持<code>SSH</code>和<code>HTTPS</code>，<code>SSH</code>支持的原生<code>Git</code>协议速度最快，<code>HTTPS</code>除了速度慢以外，还有个最大的麻烦是每次推送都必须输入口令。</p><h4 id="SSH"><a href="#SSH" class="headerlink" title="SSH"></a>SSH</h4><p>检查本机<code>SSH</code>公钥，若存在，则将<code>id_rsa.pub</code>添加到<code>Git</code>服务的<code>SSH keys</code>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ ls ~/.ssh</span><br></pre></td></tr></table></figure><p>若不存在，则生成：</p><ul><li>单个<code>Git</code>服务<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ ssh-keygen -t rsa -C &quot;your_email@youremail.com&quot;</span><br></pre></td></tr></table></figure></li><li>多个<code>Git</code>服务<figure class="highlight plaintext"><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">$ ssh-keygen -t rsa -C &quot;your_email@youremail.com&quot; -f &quot;git1_id_rsa&quot;</span><br><span class="line">$ ssh-keygen -t rsa -C &quot;your_email@youremail.com&quot; -f &quot;git2_id_rsa&quot;</span><br><span class="line">$ cp git1_id_rsa* ~/.ssh/</span><br><span class="line">$ cp git2_id_rsa* ~/.ssh/</span><br><span class="line"></span><br><span class="line"># 创建配置文件</span><br><span class="line">$ vi ~/.ssh/config</span><br><span class="line"></span><br><span class="line"># git1</span><br><span class="line">Host git1.com</span><br><span class="line">HostName git1.com</span><br><span class="line">PreferredAuthentications publickey</span><br><span class="line">IdentityFile ~/.ssh/git1_id_rsa</span><br><span class="line"></span><br><span class="line"># git2</span><br><span class="line">Host git2.com</span><br><span class="line">HostName git2.com</span><br><span class="line">PreferredAuthentications publickey</span><br><span class="line">IdentityFile ~/.ssh/git2_id_rsa</span><br></pre></td></tr></table></figure></li></ul><p>配置完成后，将对应的<code>id_rsa.pub</code>添加到<code>Git</code>服务的<code>SSH keys</code>，再次检查ssh连接情况；若不生效，则重启后再尝试：</p><figure class="highlight plaintext"><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">$ ssh -T git@github.com</span><br><span class="line">Hi! You’ve successfully authenticated, but GitHub does not provide shell access.</span><br></pre></td></tr></table></figure><blockquote><p>若出现上述信息，则表示设置成功。</p></blockquote><p>克隆：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git clone git@git.server:test.git</span><br></pre></td></tr></table></figure><h4 id="HTTPS"><a href="#HTTPS" class="headerlink" title="HTTPS"></a>HTTPS</h4><p>关闭<code>ssl</code>校验：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git config --global http.sslverify false</span><br></pre></td></tr></table></figure><p>克隆：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git clone https://git.server/test.git</span><br></pre></td></tr></table></figure><h4 id="协议切换"><a href="#协议切换" class="headerlink" title="协议切换"></a>协议切换</h4><p>查看当前协议：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git remote -v</span><br></pre></td></tr></table></figure><p>从<code>https</code>切换至<code>ssh</code>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git remote set-url origin git@domain:username/ProjectName.git</span><br></pre></td></tr></table></figure><p>从<code>ssh</code>切换至<code>https</code>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git remote set-url origin https://domain/username/ProjectName.git</span><br></pre></td></tr></table></figure><h3 id="自定义配置"><a href="#自定义配置" class="headerlink" title="自定义配置"></a>自定义配置</h3><h4 id="超时时间"><a href="#超时时间" class="headerlink" title="超时时间"></a>超时时间</h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git config --global credential.helper &#x27;cache --timeout=3600&#x27;</span><br></pre></td></tr></table></figure><h4 id="保存用户凭证"><a href="#保存用户凭证" class="headerlink" title="保存用户凭证"></a>保存用户凭证</h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git config --global credential.helper store</span><br></pre></td></tr></table></figure><p>执行后，下次操作输入的用户名和密码会被保存，后续不必手动输入用户名和密码。若同时使用不同的<code>Git</code>服务，则不推荐使用。</p><h4 id="多Git服务"><a href="#多Git服务" class="headerlink" title="多Git服务"></a>多Git服务</h4><p>若同时使用不同的<code>Git</code>服务，可以根据目录配置用户信息（需要使用<code>v2.13.0</code>及以上版本）：</p><ul><li><p>首先修改用户目录下的 <code>.gitconfig</code>，通过 <code>includeIf</code> 配置不同目录的配置文件：</p><figure class="highlight diff"><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="deletion">- [user]</span></span><br><span class="line"><span class="deletion">- name = weijie.yuan</span></span><br><span class="line"><span class="deletion">- email = weijie.yuan@gitlab.com</span></span><br><span class="line"></span><br><span class="line"><span class="addition">+ [includeIf &quot;gitdir:~/github/&quot;]</span></span><br><span class="line"><span class="addition">+     path = .gitconfig-github</span></span><br><span class="line"><span class="addition">+ [includeIf &quot;gitdir:~/gitlab/&quot;]</span></span><br><span class="line"><span class="addition">+     path = .gitconfig-gitlab</span></span><br></pre></td></tr></table></figure></li><li><p>根据配置的 <code>path</code>，分别创建 <code>.gitconfig-github</code> 文件和 <code>.gitconfig-gitlab</code> 文件：</p><figure class="highlight plaintext"><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">$ vi .gitconfig-github</span><br><span class="line">[user]</span><br><span class="line">name = weijie.yuan</span><br><span class="line">email = weijie.yuan@github.com</span><br><span class="line"></span><br><span class="line">$ vi .gitconfig-gitlab</span><br><span class="line">[user]</span><br><span class="line">name = weijie.yuan</span><br><span class="line">email = weijie.yuan@gitlab.com</span><br></pre></td></tr></table></figure><p><code>includeIf</code> 配置有如下规则：</p></li><li><p>家目录下的 <code>.gitconfig</code> ，<code>includeIf</code> 后面的 <code>path</code> 最后需要 <code>/</code> 结尾；</p></li><li><p>家目录下的 <code>.gitconfig</code> ，原有的 <code>user</code> 部分需要删除；</p></li><li><p>家目录下的 <code>.gitconfig</code> ，<code>includeIf</code>中配置的各个目录，不能是包含关系；</p></li></ul><h4 id="文本编辑器"><a href="#文本编辑器" class="headerlink" title="文本编辑器"></a>文本编辑器</h4><p><code>Linux</code> or <code>MacOS</code>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git config --global core.editor vim</span><br></pre></td></tr></table></figure><p><code>Windows</code>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&gt; git config --global core.editor &quot;&#x27;C:/Program Files/Notepad++/notepad++.exe&#x27; -multiInst -notabbar -nosession -noPlugin&quot;</span><br></pre></td></tr></table></figure><h4 id="文本比较合并工具"><a href="#文本比较合并工具" class="headerlink" title="文本比较合并工具"></a>文本比较合并工具</h4><p>查看支持的工具集合（推荐使用<code>meld</code>）：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git difftool --tool-help</span><br></pre></td></tr></table></figure><p><code>Linux</code> or <code>MacOS</code>：</p><figure class="highlight plaintext"><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">$ git config --global diff.tool meld</span><br><span class="line">$ git config --global merge.tool meld</span><br></pre></td></tr></table></figure><p><code>Windows</code>：</p><figure class="highlight plaintext"><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">&gt; git config --global diff.tool meld</span><br><span class="line">&gt; git config --global merge.tool meld</span><br><span class="line">&gt; git config --global difftool.bc3.path &#x27;C:\Program Files (x86)\Meld\Meld.exe&#x27;</span><br><span class="line">&gt; git config --global mergetool.meld.path &#x27;C:\Program Files (x86)\Meld\Meld.exe&#x27;</span><br><span class="line">&gt; git config --global difftool.meld.path &#x27;C:\Program Files (x86)\Meld\Meld.exe&#x27;</span><br></pre></td></tr></table></figure><h4 id="显示颜色"><a href="#显示颜色" class="headerlink" title="显示颜色"></a>显示颜色</h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git config --global color.ui.true</span><br></pre></td></tr></table></figure><h4 id="操作别名"><a href="#操作别名" class="headerlink" title="操作别名"></a>操作别名</h4><p>示例，将<code>checkout</code>设置为别名<code>co</code>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git config --global alias checkout co </span><br></pre></td></tr></table></figure><h3 id="查看所有配置"><a href="#查看所有配置" class="headerlink" title="查看所有配置"></a>查看所有配置</h3><figure class="highlight plaintext"><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">$ git config --local --list</span><br><span class="line">$ git config --global --list</span><br><span class="line">$ git config --system --list</span><br></pre></td></tr></table></figure><h2 id="基础操作"><a href="#基础操作" class="headerlink" title="基础操作"></a>基础操作</h2><h3 id="工作流"><a href="#工作流" class="headerlink" title="工作流"></a>工作流</h3><p><img src="/2020/09/29/git-help-v3/git-work-flow.jpg" alt="工作流"></p><p>工作区就是你的本地仓库目录，不过其中的<code>.git</code>目录不属于工作区，而是版本库，里面存了很多东西，其中最重要的就是称为<code>stage</code>（或者叫<code>index</code>）的暂存区，还有<code>Git</code>为我们自动创建的第一个分支<code>master</code>，以及指向<code>master</code>的一个指针叫<code>HEAD</code>。  </p><p>查看状态：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git status</span><br></pre></td></tr></table></figure><p>添加修改到暂存区：</p><figure class="highlight plaintext"><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">$ git add &lt;filename&gt; </span><br><span class="line">$ git add .  # 添加当前目录所有修改过的文件  </span><br><span class="line">$ git add *  # 递归地添加执行命令时所在的目录中的所有文件</span><br></pre></td></tr></table></figure><p>提交修改到版本库：</p><figure class="highlight plaintext"><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">$ git commit -m &quot;commit message&quot;</span><br><span class="line">$ git commit -am &quot;commit message&quot; # am：将添加和提交合并为一步，但只对本来就存在的文件有效</span><br></pre></td></tr></table></figure><blockquote><p><code>commit message</code>的填写可以参考<a href="http://blog.jobbole.com/92713/">写好 Git Commit 信息的 7 个建议</a>。</p></blockquote><p>现在来解释一下前面的添加和提交操作：  </p><ol><li><code>git add</code>：把文件修改添加到暂存区；</li><li><code>git commit</code>：把暂存区的所有内容提交到当前分支，即版本库；</li></ol><h3 id="版本历史记录"><a href="#版本历史记录" class="headerlink" title="版本历史记录"></a>版本历史记录</h3><p>查看当前仓库所有文件的版本历史记录：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git log</span><br></pre></td></tr></table></figure><p>查看每个文件的版本历史记录：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git log &lt;filename&gt;</span><br></pre></td></tr></table></figure><p>查看包含指定关键字的版本历史记录：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git log --grep=&quot;keywords&quot;</span><br></pre></td></tr></table></figure><p>查看指定时间段的版本历史记录，如下示例时间段为<code>2020.9.23</code>全天：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git log --after=&quot;2020-9-23 00:00:00&quot; --before=&quot;2020-9-23 23:59:59&quot;</span><br></pre></td></tr></table></figure><h3 id="暂存"><a href="#暂存" class="headerlink" title="暂存"></a>暂存</h3><p>当你需要切换分支时，若当前工作区还有些修改没有完成、又不适合提交的，操作切换分支是会提示出错的，这时就需要将这些修改暂存起来：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git stash save &quot;message&quot;</span><br></pre></td></tr></table></figure><p>查看:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git stash list</span><br></pre></td></tr></table></figure><p>恢复:</p><figure class="highlight plaintext"><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">$ git stash pop [--index] [stash@&#123;num&#125;]　</span><br><span class="line">  or</span><br><span class="line">$ git stash apply [--index] [stash@&#123;num&#125;]　# 不删除已恢复的进度.</span><br></pre></td></tr></table></figure><blockquote><p><code>--index</code>表示不仅恢复工作区,还会恢复暂存区；<code>num</code>是你要恢复的操作的序列号,默认恢复最新进度。</p></blockquote><p>删除进度:</p><figure class="highlight plaintext"><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">$ git stash drop [stash@&#123;num&#125;] # 删除指定进度</span><br><span class="line">$ git stash clear # 删除所有</span><br></pre></td></tr></table></figure><h3 id="撤销与回退"><a href="#撤销与回退" class="headerlink" title="撤销与回退"></a>撤销与回退</h3><p>查看当前仓库状态：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git status</span><br></pre></td></tr></table></figure><p>查看文件更改：</p><figure class="highlight plaintext"><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">$ git difftool &lt;filename&gt;</span><br><span class="line">$ git mergetool &lt;filename&gt;</span><br></pre></td></tr></table></figure><p>查看提交历史：</p><figure class="highlight plaintext"><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">$ git log</span><br><span class="line">$ git log --pretty=oneline #只保留commit id 和 commit message</span><br></pre></td></tr></table></figure><p>撤销工作区<code>Tracked files</code>的修改：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git checkout -- &lt;filename&gt;</span><br></pre></td></tr></table></figure><p>撤销工作区<code>Untracked files</code>的修改：</p><ul><li>n：查看将会删除的文件，防止误删；</li><li>f：<code>Untracked</code>的文件；</li><li>d：<code>Untracked</code>的目录；</li><li>x：包含<code>gitignore</code>的<code>Untracked</code>文件和目录一并删掉，慎用！；</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git clean -nfd</span><br></pre></td></tr></table></figure><p>只回退暂存区(<code>git add</code>)，不删除工作空间代码：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git reset HEAD &lt;filename&gt; # 无filename则默认回退全部 </span><br></pre></td></tr></table></figure><p>回退版本区(<code>git commit</code>)和暂存区(<code>git add</code>)，不删除工作空间代码：</p><figure class="highlight plaintext"><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">$ git reset --mixed HEAD^ # --mixed为默认参数</span><br><span class="line">$ git reset HEAD^</span><br></pre></td></tr></table></figure><p>回退版本区(<code>git commit</code>)，但是不回退暂存区(<code>git add</code>)，不删除工作空间代码：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git reset --soft HEAD^</span><br></pre></td></tr></table></figure><p>回退版本区(<code>git commit</code>)和暂存区(<code>git add</code>)，并删除工作空间代码(不包括<code>Untracked files</code>)，执行后直接恢复到指定<code>&lt;commit-id&gt;</code>状态：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git reset --hard &lt;commit-id&gt;</span><br></pre></td></tr></table></figure><blockquote><p><code>HEAD</code>表示当前版本，<code>HEAD^</code>表示上个版本，<code>HEAD^^</code>表示上上个版本，上100个版本可以表示为<code>HEAD~100</code>以此类推。</p></blockquote><p>回退版本后，若需要返回原来的版本，会发现找不到未来的<code>commit id</code>，则需要查看操作命令历史进行查找：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git reflog</span><br></pre></td></tr></table></figure><p>从版本库删除文件：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git rm &lt;filename&gt;</span><br></pre></td></tr></table></figure><p>若你的代码已经<code>push</code>到线上，则推荐使用下面这个命令回滚：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git revert &lt;commit-id&gt;</span><br></pre></td></tr></table></figure><blockquote><p><code>revert</code>是用一次新的<code>commit</code>来回滚之前的<code>commit</code>，更安全;<code>reset</code>则是直接删除指定的<code>commit</code>，若直接<code>push</code>会导致冲突。</p></blockquote><h3 id="使用帮助"><a href="#使用帮助" class="headerlink" title="使用帮助"></a>使用帮助</h3><p>查看帮助：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git --help</span><br></pre></td></tr></table></figure><h2 id="仓库管理"><a href="#仓库管理" class="headerlink" title="仓库管理"></a>仓库管理</h2><h3 id="推送本地修改到远程仓库"><a href="#推送本地修改到远程仓库" class="headerlink" title="推送本地修改到远程仓库"></a>推送本地修改到远程仓库</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git push -u origin &lt;feature-branch-name&gt;</span><br></pre></td></tr></table></figure><blockquote><p><code>-u</code>选项可以将本地分支与远程分支关联,下次<code>git pull</code>操作时可以不带参数.具体参见<a href="http://stackoverflow.com/questions/5697750/what-exactly-does-the-u-do-git-push-u-origin-master-vs-git-push-origin-ma">这里</a>。</p></blockquote><h3 id="添加本地仓库到远程"><a href="#添加本地仓库到远程" class="headerlink" title="添加本地仓库到远程"></a>添加本地仓库到远程</h3><figure class="highlight plaintext"><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">$ cd repo</span><br><span class="line">$ git init</span><br><span class="line">$ git remote add origin git@github.com:USERNAME/repo.git</span><br></pre></td></tr></table></figure><blockquote><p><code>origin</code>就是一个名字，是<code>git</code>为你默认创建的指向这个远程代码库的标签。</p></blockquote><h3 id="获取远程仓库"><a href="#获取远程仓库" class="headerlink" title="获取远程仓库"></a>获取远程仓库</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git clone git@github.com:USERNAME/repo.git</span><br></pre></td></tr></table></figure><h3 id="查看远程仓库"><a href="#查看远程仓库" class="headerlink" title="查看远程仓库"></a>查看远程仓库</h3><figure class="highlight plaintext"><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">$ git remote -v</span><br><span class="line">origin git@github.com:USERNAME/repo.git (push)</span><br><span class="line">origin git@github.com:USERNAME/repo.git (fetch)</span><br></pre></td></tr></table></figure><h3 id="关联远程仓库"><a href="#关联远程仓库" class="headerlink" title="关联远程仓库"></a>关联远程仓库</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git remote add upstream git@github.com:USERNAME/repo.git</span><br></pre></td></tr></table></figure><h3 id="同步远程仓库的更新"><a href="#同步远程仓库的更新" class="headerlink" title="同步远程仓库的更新"></a>同步远程仓库的更新</h3><figure class="highlight plaintext"><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">$ git remote -v</span><br><span class="line">origin  git@github.com:USERNAME/repo.git (push)</span><br><span class="line">origin  git@github.com:USERNAME/repo.git (fetch)</span><br><span class="line">upstream  git@github.com:USERNAME/repo.git  (push)</span><br><span class="line">upstream  git@github.com:USERNAME/repo.git (fetch)</span><br><span class="line"></span><br><span class="line">$ git fetch upstream </span><br><span class="line">$ git difftool &lt;branch-name&gt; upstream/master</span><br><span class="line">$ git merge upstream/master</span><br><span class="line">$ git mergetool</span><br></pre></td></tr></table></figure><h3 id="仓库引用（子仓库）"><a href="#仓库引用（子仓库）" class="headerlink" title="仓库引用（子仓库）"></a>仓库引用（子仓库）</h3><p><code>Git</code>包含<code>submodule</code>和<code>subtree</code>两种引用方式，官方推荐使用<a href="http://aoxuis.me/post/2013-08-06-git-subtree">subtree</a>替代<code>submodule</code>：</p><h4 id="submodule"><a href="#submodule" class="headerlink" title="submodule"></a>submodule</h4><h5 id="添加子模块"><a href="#添加子模块" class="headerlink" title="添加子模块"></a>添加子模块</h5><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git submodule add git@github.com:USERNAME/repo.git &lt;submodule-path&gt;</span><br></pre></td></tr></table></figure><p>执行成功后，暂存区会有两个修改：<code>.gitmodules</code>和命令中<code>&lt;submodule-path&gt;</code>指定的路径。</p><p>提交更新：</p><figure class="highlight plaintext"><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">$ git commit</span><br><span class="line">$ git push</span><br></pre></td></tr></table></figure><h5 id="使用子模块"><a href="#使用子模块" class="headerlink" title="使用子模块"></a>使用子模块</h5><p>克隆使用了子模块的项目后，默认其子模块目录为空，需要在项目根目录执行如下命令单独下载：</p><figure class="highlight plaintext"><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">$ git submodule update --init --recursive</span><br><span class="line"></span><br><span class="line">or</span><br><span class="line"></span><br><span class="line">$ git submodule init</span><br><span class="line">$ git submodule update</span><br></pre></td></tr></table></figure><h5 id="更新子模块"><a href="#更新子模块" class="headerlink" title="更新子模块"></a>更新子模块</h5><p>子模块仓库更新后，使用子模块的项目必须手动更新才能同步最新的提交：</p><figure class="highlight plaintext"><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">$ cd &lt;submodule-path&gt;</span><br><span class="line">$ git pull</span><br></pre></td></tr></table></figure><p>完成后返回项目根目录，可以看到子模块有待提交的更新，执行提交即可：</p><figure class="highlight plaintext"><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">$ git add .</span><br><span class="line">$ git commit</span><br><span class="line">$ git push</span><br></pre></td></tr></table></figure><h5 id="删除子模块"><a href="#删除子模块" class="headerlink" title="删除子模块"></a>删除子模块</h5><p>删除子模块目录及源码：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$  rm -rf &lt;submodule-path&gt;</span><br></pre></td></tr></table></figure><p>删除项目根目录下<code>.gitmodules</code>文件中待删除的子模块相关条目：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ vi .gitmodules </span><br></pre></td></tr></table></figure><p>删除版本库下的子模块目录，每个子模块对应一个目录，只删除对应的子模块目录即可：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">rm -rf .git/module/&lt;submodule-path&gt;</span><br></pre></td></tr></table></figure><p>删除子模块缓存：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git rm --cached &lt;submodule-path&gt;</span><br></pre></td></tr></table></figure><p>提交更新：</p><figure class="highlight plaintext"><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">$ git add .</span><br><span class="line">$ git commit</span><br><span class="line">$ git push</span><br></pre></td></tr></table></figure><h4 id="subtree"><a href="#subtree" class="headerlink" title="subtree"></a>subtree</h4><figure class="highlight plaintext"><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><br><span class="line">$ git remote add -f &lt;remote-subtree-repository-name&gt; &lt;remote-subtree-repository-url&gt;</span><br><span class="line">$ git subtree add --prefix=&lt;local-subtree-directory&gt; &lt;remote-subtree-repository&gt; &lt;remote-subtree-branch-name&gt; --squash</span><br><span class="line"></span><br><span class="line"># 同步subtree的更新</span><br><span class="line">$ git subtree pull --prefix=&lt;local-subtree-directory&gt; &lt;remote-subtree-repository&gt; &lt;remote-subtree-branch-name&gt; --squash</span><br><span class="line"></span><br><span class="line"># 推送到远程subtree库</span><br><span class="line">$ git subtree push --prefix=&lt;local-subtree-directory&gt; &lt;remote-subtree-repository&gt; &lt;remote-subtree-branch-name&gt;</span><br></pre></td></tr></table></figure><h3 id="清理仓库"><a href="#清理仓库" class="headerlink" title="清理仓库"></a>清理仓库</h3><h4 id="清理本地无效的远程追踪分支"><a href="#清理本地无效的远程追踪分支" class="headerlink" title="清理本地无效的远程追踪分支"></a>清理本地无效的远程追踪分支</h4><figure class="highlight plaintext"><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">$ git pull # 拉取更新</span><br><span class="line">$ git remote prune origin --dry-run # 列出所有可以从本地仓库中删除的远程追踪分支</span><br><span class="line">$ git remote prune origin # 清理本地无效的远程追踪分支</span><br></pre></td></tr></table></figure><h4 id="清理无用的分支和标签"><a href="#清理无用的分支和标签" class="headerlink" title="清理无用的分支和标签"></a>清理无用的分支和标签</h4><figure class="highlight plaintext"><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">$ git branch -d &lt;branch-name&gt;</span><br><span class="line">$ git tag -d &lt;tag-name&gt;</span><br></pre></td></tr></table></figure><h4 id="清理大文件"><a href="#清理大文件" class="headerlink" title="清理大文件"></a>清理大文件</h4><ul><li><p>查看仓库占用空间：</p><figure class="highlight plaintext"><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">$ git count-objects -v</span><br><span class="line">$ du -sh .git</span><br></pre></td></tr></table></figure></li><li><p>寻找大文件<code>ID</code>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git verify-pack -v .git/objects/pack/*.idx | sort -k 3 -n | tail -10</span><br></pre></td></tr></table></figure><blockquote><p>输出的第一列是文件<code>ID</code>，第二列表示文件（<code>blob</code>）或目录（<code>tree</code>），第三列是文件大小，此处筛选了最大的10条。</p></blockquote></li><li><p>根据文件<code>ID</code>映射文件名：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git rev-list --objects --all | grep &quot;$(git verify-pack -v .git/objects/pack/*.idx | sort -k 3 -n | tail -10 | awk &#x27;&#123;print$1&#125;&#x27;)&quot;</span><br></pre></td></tr></table></figure></li><li><p>根据文件名，从所有提交中删除文件：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git filter-branch --force --index-filter &#x27;git rm -rf --cached --ignore-unmatch [FileName]&#x27; --prune-empty --tag-name-filter cat -- --all</span><br></pre></td></tr></table></figure></li><li><p>删除缓存下来的<code>ref</code>和<code>git</code>操作记录：</p><figure class="highlight plaintext"><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">$ git for-each-ref --format=&#x27;delete %(refname)&#x27; refs/original | git update-ref --stdin</span><br><span class="line">$ git reflog expire --expire=now --all</span><br></pre></td></tr></table></figure></li><li><p>清理<code>.git</code>目录并推送到远程：</p><figure class="highlight plaintext"><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">$ git gc --prune=now</span><br><span class="line">$ git push -f --all</span><br></pre></td></tr></table></figure><blockquote><p>在执行<code>push</code>操作时，<code>git</code>会自动执行一次<code>gc</code>操作，不过只有<code>loose object</code>达到一定数量后才会真正调用，建议手动执行。</p></blockquote></li><li><p>重新查看仓库占用空间，发现较清理前变小很多：</p><figure class="highlight plaintext"><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">$ git count-objects -v</span><br><span class="line">$ du -sh .git</span><br></pre></td></tr></table></figure></li></ul><h4 id="清理大型二进制文件"><a href="#清理大型二进制文件" class="headerlink" title="清理大型二进制文件"></a>清理大型二进制文件</h4><p>由于<code>Git</code>在存储二进制文件时效率不高，所以需要借助<a href="http://www.oschina.net/news/71365/git-annex-lfs-bigfiles-fat-media-bigstore-sym">第三方组件</a>。</p><h2 id="分支管理"><a href="#分支管理" class="headerlink" title="分支管理"></a>分支管理</h2><h3 id="查看分支"><a href="#查看分支" class="headerlink" title="查看分支"></a>查看分支</h3><p>查看所有分支：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git branch -a</span><br></pre></td></tr></table></figure><blockquote><p>有<code>*</code>标记的是当前分支。</p></blockquote><p>查看某个<code>&lt;commit id&gt;</code>属于哪个分支:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git branch -a --contains &lt;commit id&gt;</span><br></pre></td></tr></table></figure><h3 id="创建分支"><a href="#创建分支" class="headerlink" title="创建分支"></a>创建分支</h3><p>在本地创建分支：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git branch &lt;newbranch&gt; # 创建</span><br></pre></td></tr></table></figure><p>在本地创建分支并切换：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git checkout -b &lt;newbranch&gt; # 创建并切换</span><br></pre></td></tr></table></figure><p>从标签创建分支：</p><figure class="highlight plaintext"><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">$ git branch &lt;newbranch&gt; &lt;tagname&gt;</span><br><span class="line">$ git checkout &lt;newbranch&gt; # 切换到新建分支</span><br></pre></td></tr></table></figure><p>获取远程分支到本地并创建本地分支：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git checkout -b &lt;local-branch&gt; &lt;remote-branch&gt;</span><br></pre></td></tr></table></figure><p>推送新建本地分支到远程：</p><figure class="highlight plaintext"><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">$ git push -u origin &lt;remote-branch-name&gt;</span><br><span class="line">  or</span><br><span class="line">$ git push --set-upstream origin &lt;remote-branch-name&gt;</span><br></pre></td></tr></table></figure><h3 id="创建空白分支"><a href="#创建空白分支" class="headerlink" title="创建空白分支"></a>创建空白分支</h3><p>创建一个分支，该分支会包含父分支的所有文件，但不会指向任何历史提交：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git checkout --orphan &lt;newbranch&gt;</span><br></pre></td></tr></table></figure><p>删除所有文件：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git rm -rf .</span><br></pre></td></tr></table></figure><p>提交分支：</p><figure class="highlight plaintext"><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">$ echo &#x27;# new branch&#x27; &gt;&gt; README.md</span><br><span class="line">$ git add README.md</span><br><span class="line">$ git commit</span><br><span class="line">$ git push origin &lt;remote-branch-name&gt;</span><br></pre></td></tr></table></figure><h3 id="删除分支"><a href="#删除分支" class="headerlink" title="删除分支"></a>删除分支</h3><p>删除本地分支：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git branch -d &lt;branch&gt;</span><br></pre></td></tr></table></figure><blockquote><p>若当前分支因为有修改未提交或其它情况不能删除，请使用<code>-D</code>选项强制删除。</p></blockquote><p>清理无用的本地分支：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git remote prune origin</span><br></pre></td></tr></table></figure><blockquote><p>通常在<code>remote</code>上的分支被删除后，更新本地分支列表时使用。</p></blockquote><p>删除远程分支(三种方法)：</p><figure class="highlight plaintext"><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">$ git push origin --delete &lt;remote-branch-name&gt;</span><br><span class="line">$ git push origin -d &lt;remote-branch-name&gt;</span><br><span class="line">$ git push origin :&lt;remote-branch-name&gt;</span><br></pre></td></tr></table></figure><h3 id="更新分支"><a href="#更新分支" class="headerlink" title="更新分支"></a>更新分支</h3><p>获取远程分支到本地已有分支：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git branch --set-upstream &lt;local-branch&gt; origin/branch</span><br></pre></td></tr></table></figure><p>同步当前分支的所有更新，使用<code>git pull</code>并不保险：</p><figure class="highlight plaintext"><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"># 下载最新的代码到远程跟踪分支, 即origin/&lt;branch-name&gt;</span><br><span class="line">$ git fetch origin &lt;branch-name&gt; </span><br><span class="line"># 查看更新内容</span><br><span class="line">$ git difftool &lt;branch-name&gt; origin/&lt;branch-name&gt;</span><br><span class="line"># 尝试合并远程跟踪分支的代码到本地分支 </span><br><span class="line">$ git merge origin/&lt;branch-name&gt;</span><br><span class="line"># 借助mergetool解决冲突              </span><br><span class="line">$ git mergetool                               </span><br></pre></td></tr></table></figure><p>同步其它分支的所有更新，本例拉取<code>master</code>分支更新：</p><figure class="highlight plaintext"><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">$ git fetch origin master</span><br><span class="line">$ git difftool &lt;branch-name&gt; origin/master</span><br><span class="line">$ git merge origin/master</span><br><span class="line">$ git mergetool</span><br></pre></td></tr></table></figure><p>同步其它分支的部分更新，即同步某几次提交：</p><figure class="highlight plaintext"><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"># 同步提交A</span><br><span class="line">$ git cherry-pick &lt;commit id A&gt; </span><br><span class="line"># 同步提交A和B</span><br><span class="line">$ git cherry-pick &lt;commit id A&gt; &lt;commit id B&gt; </span><br><span class="line"># 同步提交A到B的所有提交（不包括A），提交A必须早于提交B，否则命令将失败，但不会报错</span><br><span class="line">$ git cherry-pick &lt;commit id A&gt;..&lt;commit id B&gt; </span><br><span class="line"># 同步提交A到B的所有提交（包括A），提交A必须早于提交B，否则命令将失败，但不会报错</span><br><span class="line">$ git cherry-pick &lt;commit id A&gt;^..&lt;commit id B&gt; </span><br></pre></td></tr></table></figure><h2 id="标签管理"><a href="#标签管理" class="headerlink" title="标签管理"></a>标签管理</h2><h3 id="查看标签"><a href="#查看标签" class="headerlink" title="查看标签"></a>查看标签</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git tag</span><br></pre></td></tr></table></figure><h3 id="创建标签"><a href="#创建标签" class="headerlink" title="创建标签"></a>创建标签</h3><figure class="highlight plaintext"><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">$ git tag -a &lt;tagname&gt; -m &quot;tag message&quot; # 创建标签在当前最新提交的commit上</span><br><span class="line">$ git tag -a &lt;tagname&gt; -m &quot;tag message&quot; &lt;commit id&gt; # 创建标签在指定的commit上</span><br></pre></td></tr></table></figure><blockquote><p>若创建标签基于的<code>commit</code>被删除，标签不会被影响，依旧存在。</p></blockquote><h3 id="推送标签"><a href="#推送标签" class="headerlink" title="推送标签"></a>推送标签</h3><p>推送标签到远程服务器：</p><figure class="highlight plaintext"><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">$ git push origin &lt;tagname&gt; # 推送一个本地标签</span><br><span class="line">$ git push origin --tags # 推送全部未推送过的本地标签</span><br></pre></td></tr></table></figure><h3 id="删除标签"><a href="#删除标签" class="headerlink" title="删除标签"></a>删除标签</h3><figure class="highlight plaintext"><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">$ git tag -d &lt;tagname&gt; # 删除一个本地标签</span><br><span class="line">$ git push origin :refs/tags/&lt;tagname&gt; # 删除一个远程标签</span><br></pre></td></tr></table></figure><h2 id="进阶技巧"><a href="#进阶技巧" class="headerlink" title="进阶技巧"></a>进阶技巧</h2><h3 id="忽略特殊文件"><a href="#忽略特殊文件" class="headerlink" title="忽略特殊文件"></a>忽略特殊文件</h3><p>当你的仓库中有一些文件，类似密码或者数据库文件不需要提交但又必须放在仓库目录下，每次<code>git status</code>都会提示<code>Untracked</code>，看着让人很不爽，提供两种方法解决这个问题。</p><h4 id="本地忽略"><a href="#本地忽略" class="headerlink" title="本地忽略"></a>本地忽略</h4><p>在代码仓库目录创建一个<code>.gitignore</code>文件，编写规则如下：</p><figure class="highlight plaintext"><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">tmp/  # 忽略tmp文件夹下所有内容</span><br><span class="line">*.ini # 忽略所有ini文件</span><br><span class="line">!data/ #忽略除了data文件夹的所有内容</span><br></pre></td></tr></table></figure><blockquote><p><a href="https://github.com/github/gitignore"><code>.gitignore</code>模版</a></p></blockquote><h4 id="全局忽略"><a href="#全局忽略" class="headerlink" title="全局忽略"></a>全局忽略</h4><p>在用户目录创建一个<code>.gitignore_global</code>文件，编写规则同<code>.gitignore</code>，并修改<code>~/.gitconfig</code>：</p><figure class="highlight plaintext"><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">[core]</span><br><span class="line">excludesfile = ~/.gitignore_global</span><br></pre></td></tr></table></figure><p>如果添加的忽略对象已经<code>Tracked</code>，纳入了版本管理中，则需要在代码仓库中先把本地缓存删除,改变成<code>Untracked</code>状态:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git rm -r --cached .</span><br></pre></td></tr></table></figure><h3 id="重写历史（慎用！）"><a href="#重写历史（慎用！）" class="headerlink" title="重写历史（慎用！）"></a>重写历史（慎用！）</h3><h4 id="修改历史提交（变基）"><a href="#修改历史提交（变基）" class="headerlink" title="修改历史提交（变基）"></a>修改历史提交（变基）</h4><figure class="highlight plaintext"><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">$ git rebase -i [git-hash| head~n]</span><br><span class="line">$ git push -f # 不强制 push 会多一条 merge 提交信息</span><br></pre></td></tr></table></figure><p>其中<code>git-hash</code>是你要开始进行<code>rebase</code>的<code>commit</code>的<code>hash</code>，而<code>head~n</code>则是从<code>HEAD</code>向前推<code>n</code>个<code>commit</code></p><h4 id="修改最近一次提交信息"><a href="#修改最近一次提交信息" class="headerlink" title="修改最近一次提交信息"></a>修改最近一次提交信息</h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git commit --amend</span><br></pre></td></tr></table></figure><h4 id="修改提交记录中的用户信息"><a href="#修改提交记录中的用户信息" class="headerlink" title="修改提交记录中的用户信息"></a>修改提交记录中的用户信息</h4><p>修改最近一次提交的用户信息：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git commit --amend --author=&quot;GIT_AUTHOR_NAME &lt;GIT_AUTHOR_EMAIL&gt;&quot;</span><br></pre></td></tr></table></figure><p>全局修改用户信息：</p><figure class="highlight plaintext"><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">$ git filter-branch --commit-filter &#x27;</span><br><span class="line">        if [ &quot;$GIT_AUTHOR_EMAIL&quot; = &quot;xxx@localhost&quot; ];</span><br><span class="line">        then</span><br><span class="line">                GIT_AUTHOR_NAME=&quot;xxx&quot;;</span><br><span class="line">                GIT_AUTHOR_EMAIL=&quot;xxx@example.com&quot;;</span><br><span class="line">                git commit-tree &quot;$@&quot;;</span><br><span class="line">        else</span><br><span class="line">                git commit-tree &quot;$@&quot;;</span><br><span class="line">        fi&#x27; HEAD --all</span><br></pre></td></tr></table></figure><h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><ul><li><a href="http://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000">廖雪峰老师的git教程</a></li><li><a href="http://www.ruanyifeng.com/blog/2015/12/git-cheat-sheet.html">常用Git命令清单</a></li><li><a href="https://git-scm.com/book/en/v2">Git-Book</a></li><li><a href="https://git-scm.com/docs">Git-Reference</a></li><li><a href="https://segmentfault.com/a/1190000002783245">Git push与pull的默认行为</a></li><li><a href="https://github.com/k88hudson/git-flight-rules/blob/master/README_zh-CN.md">Git飞行规则(Flight Rules)</a></li><li><a href="http://www.ayqy.net/blog/%E7%90%86%E8%A7%A3git-submodules/">理解Git Submodules</a></li><li><a href="https://rouroux.github.io/git-submodule/">git中submodule子模块的添加、使用和删除</a></li><li><a href="https://harttle.land/2016/03/22/purge-large-files-in-gitrepo.html">寻找并删除 Git 记录中的大文件</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;p&gt;本文是对&lt;a href=&quot;https://answerywj.com/2019/02/12/git-help-v2/&quot;&gt;Git速查手册（第二版）&lt;/a&gt;的更新，补充了一些近期使用或者收集的一些命令。&lt;/p&gt;</summary>
    
    
    
    <category term="Git" scheme="https://answerywj.com/categories/Git/"/>
    
    
    <category term="git" scheme="https://answerywj.com/tags/git/"/>
    
  </entry>
  
  <entry>
    <title>写好技术简历的一些建议</title>
    <link href="https://answerywj.com/2020/07/03/gudie-of-technical-resume/"/>
    <id>https://answerywj.com/2020/07/03/gudie-of-technical-resume/</id>
    <published>2020-07-03T03:00:04.000Z</published>
    <updated>2023-03-20T06:26:35.322Z</updated>
    
    <content type="html"><![CDATA[<p>根据我求职与招聘的经验，结合网上相关资料，这里整理了一些写好技术简历的建议。</p><span id="more"></span><blockquote><p>这里提供了一份<a href="https://github.com/AnSwErYWJ/resume/blob/master/template.md">简历模版</a>，可供参考。</p></blockquote><h2 id="保证内容真实性"><a href="#保证内容真实性" class="headerlink" title="保证内容真实性"></a>保证内容真实性</h2><p>写简历必须要保证内容的真实性，这是候选人个人诚信的体现，无论在简历中如何自夸，最终会在面试过程中也会露怯；同时现在大部分企业在招聘时，都会进行背调，所以请务必保证简历内容包括面试过程的真实性，实事求是，<strong>“知之为知之，不知为不知”</strong>。</p><h2 id="项目描述不是流水账"><a href="#项目描述不是流水账" class="headerlink" title="项目描述不是流水账"></a>项目描述不是流水账</h2><p>对于技术人员来说，项目经历是简历的主体，也是简历中最需要突出的部分，项目中涉及的技能和成果等，是对求职者能力最具说服力的证明。</p><p>据说，谷歌要求求职者描述项目经历时，必须提供下面三个信息：</p><ul><li>做了什么产品</li><li>用到了什么技术</li><li>取得了什么结果（<strong>量化</strong>）</li></ul><p>这里我认为还可以增加一个信息，即在项目中的角色：</p><ul><li>独立完成开发</li><li>协作开发，为主导开发，或辅助开发</li><li>团队领导者，团队规模为多少人</li></ul><p>比如，”在 <code>xx</code> 产品中，主导开发了 <code>xx</code> 功能，使用了 <code>xx</code> 技术，解决了 <code>xx</code> 问题，带来了<code> xx</code> 增长或收入”。</p><blockquote><p>特别需要注意的是，取得的结果一定要量化，如功能被多少人使用、性能提升多少倍、节约了多少成本、带来了多少收益等等</p></blockquote><p>除此之外，还请注意下面几点：</p><ul><li>挑选近 <code>3~5</code> 年内的项目，除非特别有价值，否则不要描述过于旧的项目；</li><li>将项目分类，每类挑选 <code>2~3</code> 个核心的项目详细描述，其余可一笔带过；</li><li>量化项目结果时，注重实际收益，如用户增长、经济收益等；</li></ul><h2 id="筛选专业技能"><a href="#筛选专业技能" class="headerlink" title="筛选专业技能"></a>筛选专业技能</h2><p>我遇到过很多刚毕业或毕业不久的求职者，简历中的专业技能十分齐全，但是面试过程中却发现大多的技能，仅仅是接触过或者在学校学过，这会给人感觉<strong>“你什么都会一点，但是什么都不精通”</strong>，这样做的伤害大于帮助。</p><p>所以，请筛选你的专业技能，建议可以按照熟练程度进行分类：</p><ul><li><strong>了解</strong>：有使用过该技能做过一点东西，清楚其使用场景、能够解决的问题；若面试官提及，能够接上话；</li><li><strong>熟悉</strong>：经常性使用该技能、以该技能为主进行研发，对其原理有初步了解；若面试官提及，能够有来有回的聊起来；</li><li><strong>精通（慎用）</strong>：熟练使用该技能，对其原理理解透彻；若面试官提及，能够直接碾压他！</li></ul><p>最后，与专业无关的技能切勿出现在简历中，那这些内容对企业没用，缺乏针对性，只会浪费简历空间，如在校上过的课程、得过的奖学金、当过学生会干部，会计证、驾驶证、英语四六级考试等。</p><h2 id="具有岗位相关性"><a href="#具有岗位相关性" class="headerlink" title="具有岗位相关性"></a>具有岗位相关性</h2><p>企业招聘的目的，是希望找到能够帮助它干活或解决问题的人，仅仅通过一份简历，它也没有把握什么样的人匹配这个岗位，所以它只能假设如果你掌握了这个岗位所需要的某几种核心专业技能，你就是初步合格的候选人。</p><p>所以简历应该针对每个应聘的岗位，突出该岗位所需要的项目经历和专业技能，来证明你可以胜任这份工作。</p><h2 id="页数不超过三页"><a href="#页数不超过三页" class="headerlink" title="页数不超过三页"></a>页数不超过三页</h2><p>简历是求职者用来向企业传递信息的，一般来说企业招聘人员在筛选简历时，阅读简历的时间不会超过半分钟，如果看不到想要的点，就会直接 <code>pass</code>；所以一定要突出重点内容，不要写得密密麻麻，堆砌各种无关的信息，这样只会增加简历的阅读难度，让招聘人员抓不到重点，对于技术人员来说，重点就是你的项目经历以及专业技能。</p><h2 id="避免错别字和语法错误"><a href="#避免错别字和语法错误" class="headerlink" title="避免错别字和语法错误"></a>避免错别字和语法错误</h2><p>简历中应该避免出现错别字、语法错误等低级错误，这很有可能会给企业留下不严谨、做事马虎的印象；同时技术名词大小写也应该遵循业内规范，保持专业性。</p><p>如果对自己不放心，可以在发送简历之前，请朋友检查一遍简历。</p><h2 id="自我评价"><a href="#自我评价" class="headerlink" title="自我评价"></a>自我评价</h2><p>一般简历中，都会要求候选人进行自我评价，请注意不要描述自己的工作态度，如积极进取、工作努力等，这是默认应该做到的，不是加分项。<br>自我评价可以针对如下四点进行：</p><ul><li>学习能力</li><li>沟通能力</li><li>协作能力</li><li>执行能力</li></ul><h2 id="优化细节"><a href="#优化细节" class="headerlink" title="优化细节"></a>优化细节</h2><p>优化企业招聘人员在阅读简历时的体验，使你的简历比别人读起来更舒服，自然简历通过的机会也会更高，可以关注以下细节：</p><ul><li>在各类联系方式后面，注明最优联系时间，以免错过面试邀约；</li><li>若手机为外地号码，最好注明；</li><li>简历使用 <code>markdown</code> 编写，转换成 <code>pdf</code> 格式发出，可以避免样式丢失或格式不兼容；</li></ul><h2 id="写在最后"><a href="#写在最后" class="headerlink" title="写在最后"></a>写在最后</h2><p>重中之重，还是需要提高自己的能力，正所谓 <strong>“酒香不怕巷子深“</strong>。</p><h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><ul><li><a href="https://dev.to/gemography/common-mistakes-in-dev-cvs-2a17">8 Hacks For Your Next Tech Resume</a></li><li><a href="http://www.ruanyifeng.com/blog/2020/01/technical-resume.html">如何写一份有效的技术简历？</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;p&gt;根据我求职与招聘的经验，结合网上相关资料，这里整理了一些写好技术简历的建议。&lt;/p&gt;</summary>
    
    
    
    <category term="科普" scheme="https://answerywj.com/categories/%E7%A7%91%E6%99%AE/"/>
    
    
    <category term="resume" scheme="https://answerywj.com/tags/resume/"/>
    
    <category term="技术简历" scheme="https://answerywj.com/tags/%E6%8A%80%E6%9C%AF%E7%AE%80%E5%8E%86/"/>
    
    <category term="求职招聘" scheme="https://answerywj.com/tags/%E6%B1%82%E8%81%8C%E6%8B%9B%E8%81%98/"/>
    
  </entry>
  
  <entry>
    <title>--hash-style兼容性问题</title>
    <link href="https://answerywj.com/2020/05/14/ld-hash-style/"/>
    <id>https://answerywj.com/2020/05/14/ld-hash-style/</id>
    <published>2020-05-14T07:54:25.000Z</published>
    <updated>2023-03-20T06:26:35.334Z</updated>
    
    <content type="html"><![CDATA[<p>本文记录了解决 <code>--hash-style</code> 兼容性问题的过程。</p><span id="more"></span><h2 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">dlopen failed: empty/missing DT_HASH in &quot;libxxx.so&quot; (built with --hash-style=gnu?)</span><br></pre></td></tr></table></figure><p>最近，稳定性监控平台，被这一行错误日志霸榜，刚看到时也一脸懵逼，下面我们来逐步分析。</p><h2 id="名词解释"><a href="#名词解释" class="headerlink" title="名词解释"></a>名词解释</h2><p>首先需要查阅一下相关文档，了解一下其中的”<code>新朋友</code>”。</p><ul><li><p><code>DT_HASH</code></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ELF 中的一个 Sections，保存了一个用于查找符号的散列表，用于支持符号表的访问，能够提高符号搜索速度。</span><br></pre></td></tr></table></figure></li><li><p><code>--hash-style=style</code>(以下解释摘自 <code>man ld</code>)</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Set the type of linker&#x27;s hash table(s). style can be either &quot;sysv&quot; for classic ELF &quot;.hash&quot; section, &quot;gnu&quot; for new style GNU &quot;.gnu.hash&quot; section or &quot;both&quot; for both the classic ELF &quot;.hash&quot; and new style GNU &quot;.gnu.hash&quot; hash tables. The default is &quot;sysv&quot;.</span><br></pre></td></tr></table></figure></li></ul><h2 id="实验"><a href="#实验" class="headerlink" title="实验"></a>实验</h2><p>通过查阅 <code>--hash-style=style</code> 参数，发现 <code>style</code> 支持三种配置：<code>sysv</code>、<code>gnu</code>和<code>both</code>，废话不多说，先试一把。</p><ul><li><p><code>gcc -Wl,--hash-style=sysv</code></p><figure class="highlight plaintext"><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 -S libxxx.so  | grep &quot;hash&quot;</span><br><span class="line">[ 4] .hash             HASH             0000000000003120  00003120</span><br></pre></td></tr></table></figure></li><li><p><code>gcc -Wl,--hash-style=gnu</code></p><figure class="highlight plaintext"><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 -S libxxx.so  | grep &quot;hash&quot;</span><br><span class="line">[ 4] .gnu.hash         GNU_HASH         0000000000003120  00003120</span><br></pre></td></tr></table></figure></li><li><p><code>gcc -Wl,--hash-style=both</code></p><figure class="highlight plaintext"><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">$ readelf -S libxxx.so  | grep &quot;hash&quot;</span><br><span class="line">  [ 4] .gnu.hash         GNU_HASH         0000000000003120  00003120</span><br><span class="line">  [ 5] .hash             HASH             00000000000035f8  000035f8</span><br></pre></td></tr></table></figure></li></ul><blockquote><p><code>-Wl</code> 用于编译器向链接器传递参数。  </p></blockquote><p>如上，发现使用不同的配置，<code>Sections Name</code> 不同。</p><blockquote><p><code>.gnu.hash</code> 段​，提​供了​与 <code>hash</code> 段​相​同​的​功​能​；但​是与 <code>hash</code> 相比，增加了某些限制（附加规则），​导致了不兼容，带​来​了​ <code>50%</code> 的​动​态​链​接​性​能​提​升​，具体参见 <a href="https://blogs.oracle.com/solaris/gnu-hash-elf-sections-v2">https://blogs.oracle.com/solaris/gnu-hash-elf-sections-v2</a>。</p></blockquote><h2 id="分析"><a href="#分析" class="headerlink" title="分析"></a>分析</h2><p>结合实验结果，我先翻译一下问题中的那一行错误日志：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">动态库加载失败，libxxx.so 中 DT_HASH 为空或者丢失，是不是用了 --hash-style=gnu 编译?</span><br></pre></td></tr></table></figure><p>结合上表，若 <code>--hash-style=gnu</code>，那么 <code>Section Name</code> 就是 <code>.gnu.hash</code> 了，当然找不到<code>.hash</code> 了。</p><p>再查阅一下 <code>Makefile</code> 配置，发现并没有相关配置，怀疑是编译器的默认配置：</p><figure class="highlight plaintext"><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 -dumpspecs | grep &quot;hash&quot;</span><br><span class="line">... --hash-style=gnu ...</span><br></pre></td></tr></table></figure><blockquote><p><code>-dumpspecs</code> 参数可以打印编译器的内置规范。</p></blockquote><p>果不其然，默认的 <code>--hash-style</code> 配置为了 <code>gnu</code>。</p><h2 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h2><p>输出的动态库的 <code>--hash-style</code> 为 <code>gnu</code>，而目标系统并不能正确读取 <code>--hash-style</code> 为 <code>gnu</code> 的动态库，导致了如上的错误。</p><h2 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h2><p>配置 <code>--hash-style</code> 为 <code>both</code>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">LDFLAGS += -Wl,--hash-style=both</span><br></pre></td></tr></table></figure><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li><a href="https://stackoverflow.com/questions/11741816/only-one-hash-style-in-embedded-linux-why">Only one –hash-style in embedded Linux. Why?</a></li><li><a href="https://sites.google.com/site/avinesh/binaryincompatibilitybetweenrhel4andrhel">Binaries built on RHEL5 or FC6 do not work on RHEL4 or FC5!</a></li><li><a href="https://stackoverflow.com/questions/28638809/android-ndk-unsatisfiedlinkerror-dlopen-failed-empty-missing-dt-hash">Android NDK UnsatisfiedLinkError: “dlopen failed: empty&#x2F;missing DT_HASH”</a></li><li><a href="https://stackoverflow.com/questions/42068271/trouble-understanding-gcc-linker-options">Trouble understanding gcc linker options</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;p&gt;本文记录了解决 &lt;code&gt;--hash-style&lt;/code&gt; 兼容性问题的过程。&lt;/p&gt;</summary>
    
    
    
    <category term="编译链接" scheme="https://answerywj.com/categories/%E7%BC%96%E8%AF%91%E9%93%BE%E6%8E%A5/"/>
    
    
    <category term="gcc" scheme="https://answerywj.com/tags/gcc/"/>
    
    <category term="ld" scheme="https://answerywj.com/tags/ld/"/>
    
    <category term="hash-style" scheme="https://answerywj.com/tags/hash-style/"/>
    
  </entry>
  
  <entry>
    <title>Homebrew</title>
    <link href="https://answerywj.com/2020/02/17/homebrew-in-mac/"/>
    <id>https://answerywj.com/2020/02/17/homebrew-in-mac/</id>
    <published>2020-02-17T12:42:48.000Z</published>
    <updated>2023-03-20T06:26:35.322Z</updated>
    
    <content type="html"><![CDATA[<p>本文将介绍<code>Homebrew</code>的安装与使用。</p><span id="more"></span><h2 id="Homebrew"><a href="#Homebrew" class="headerlink" title="Homebrew"></a>Homebrew</h2><h3 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h3><p><code>Homebrew</code>是<code>OS X</code>上类似于<code>apt-get</code>和<code>yum</code>的软件包管理器，软件源依托于<code>Github</code>之上，所以在国内的网络环境之下，常常会出现使用<code>Homebrew</code>安装软件时，如<code>brew install sshfs</code>，经常会长时间卡在<code>Updating Homebrew...</code>。</p><blockquote><p><code>OS X 10.9</code>开始支持</p></blockquote><h3 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h3><p>首先安装依赖<code>Xcode命令行工具</code>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ xcode-select --install</span><br></pre></td></tr></table></figure><p>然后安装<code>Homebrew</code>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ ruby -e &quot;$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)&quot;</span><br></pre></td></tr></table></figure><h3 id="卸载"><a href="#卸载" class="headerlink" title="卸载"></a>卸载</h3><figure class="highlight plaintext"><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">$ ruby -e &quot;$(curl -fsSL $https://raw.githubusercontent.com/Homebrew/install/master/uninstall)&quot;</span><br><span class="line">$ sudo rm -rf /usr/local/</span><br></pre></td></tr></table></figure><h2 id="解决软件源更新慢问题"><a href="#解决软件源更新慢问题" class="headerlink" title="解决软件源更新慢问题"></a>解决软件源更新慢问题</h2><h3 id="取消更新"><a href="#取消更新" class="headerlink" title="取消更新"></a>取消更新</h3><p>当安装过程中，卡在<code>Updating Homebrew...</code>时，我们可以按住<code>control + c</code>，来取消本次更新；之后命令行会显示<code>^C</code>，表示取消成功，后面会继续安装工作。</p><blockquote><p>这个方法是临时，仅在本次安装生效。</p></blockquote><h3 id="关闭自动更新"><a href="#关闭自动更新" class="headerlink" title="关闭自动更新"></a>关闭自动更新</h3><p><code>Homebrew</code>的软件源更新，是在每次安装时自动执行的，可以通过配置进行关闭。</p><p><code>zsh</code>终端方式：</p><figure class="highlight plaintext"><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">$ echo &#x27;export HOMEBREW_NO_AUTO_UPDATE=true&#x27; &gt;&gt; ~/.zshrc</span><br><span class="line">$ source ~/.zshrc</span><br></pre></td></tr></table></figure><p><code>bash</code>终端方式：</p><figure class="highlight plaintext"><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">$ echo &#x27;export HOMEBREW_NO_AUTO_UPDATE=true&#x27; &gt;&gt; ~/.bash_profile</span><br><span class="line">$ source ~/.bash_profile</span><br></pre></td></tr></table></figure><blockquote><p>这个方法是永久的，每次安装都会生效，但弊端是无法获取最新的软件。</p></blockquote><h3 id="替换软件源"><a href="#替换软件源" class="headerlink" title="替换软件源"></a>替换软件源</h3><p>这里推荐中科大的镜像源，亲测可用。  </p><h4 id="替换homebrew源"><a href="#替换homebrew源" class="headerlink" title="替换homebrew源"></a>替换homebrew源</h4><figure class="highlight plaintext"><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">$ cd &quot;$(brew --repo)&quot;</span><br><span class="line">$ git remote set-url origin https://mirrors.ustc.edu.cn/brew.git</span><br></pre></td></tr></table></figure><p>还原官方源：</p><figure class="highlight plaintext"><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">$ cd &quot;$(brew --repo)&quot;</span><br><span class="line">$ git remote set-url origin https://github.com/Homebrew/brew.git</span><br></pre></td></tr></table></figure><h4 id="替换homebrew-core源-核心软件仓库"><a href="#替换homebrew-core源-核心软件仓库" class="headerlink" title="替换homebrew-core源(核心软件仓库)"></a>替换homebrew-core源(核心软件仓库)</h4><figure class="highlight plaintext"><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">$ cd &quot;$(brew --repo)/Library/Taps/homebrew/homebrew-core&quot;</span><br><span class="line">$ git remote set-url origin https://mirrors.ustc.edu.cn/homebrew-core.git</span><br></pre></td></tr></table></figure><p>还原官方源：</p><figure class="highlight plaintext"><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">$ cd &quot;$(brew --repo)/Library/Taps/homebrew/homebrew-core&quot;</span><br><span class="line">$ git remote set-url origin https://github.com/Homebrew/homebrew-core.git</span><br></pre></td></tr></table></figure><h4 id="替换homebrew-cask源-macOS应用"><a href="#替换homebrew-cask源-macOS应用" class="headerlink" title="替换homebrew-cask源(macOS应用)"></a>替换homebrew-cask源(macOS应用)</h4><figure class="highlight plaintext"><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">$ cd &quot;$(brew --repo)&quot;/Library/Taps/homebrew/homebrew-cask </span><br><span class="line">$ git remote set-url origin https://mirrors.ustc.edu.cn/homebrew-cask.git</span><br></pre></td></tr></table></figure><p>若提示找不到<code>&quot;$(brew --repo)&quot;/Library/Taps/homebrew/homebrew-cask</code>，则：</p><figure class="highlight plaintext"><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">$ cd &quot;$(brew --repo)&quot;/Library/Taps/homebrew/</span><br><span class="line">$ git clone https://mirrors.ustc.edu.cn/homebrew-cask.git</span><br></pre></td></tr></table></figure><p>还原官方源：</p><figure class="highlight plaintext"><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">$ cd &quot;$(brew --repo)&quot;/Library/Taps/homebrew/homebrew-cask  </span><br><span class="line">$ git remote set-url origin https://github.com/Homebrew/homebrew-cask</span><br></pre></td></tr></table></figure><blockquote><p><code>brew cask</code>安装软件，会自动创建软链接到<code>Application</code>目录，这样在<code>Launchpad</code>中也能查看到安装的软件，方便启动软件</p></blockquote><h4 id="替换homebrew-bottles源-预编译二进制软件包"><a href="#替换homebrew-bottles源-预编译二进制软件包" class="headerlink" title="替换homebrew bottles源(预编译二进制软件包)"></a>替换homebrew bottles源(预编译二进制软件包)</h4><p><code>zsh</code>终端方式：</p><figure class="highlight plaintext"><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">$ echo &#x27;export HOMEBREW_BOTTLE_DOMAIN=https://mirrors.ustc.edu.cn/homebrew-bottles&#x27; &gt;&gt; ~/.zshrc</span><br><span class="line">$ source ~/.zshrc</span><br></pre></td></tr></table></figure><p><code>bash</code>终端方式：</p><figure class="highlight plaintext"><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">$ echo &#x27;export HOMEBREW_BOTTLE_DOMAIN=https://mirrors.ustc.edu.cn/homebrew-bottles&#x27; &gt;&gt; ~/.bash_profile</span><br><span class="line">$ source ~/.bash_profile</span><br></pre></td></tr></table></figure><p>还原官方源：进入如上终端配置文件，并删除<code>HOMEBREW_BOTTLE_DOMAIN</code>改行配置，并<br><code>source</code>终端配置文件，使之生效。</p><h2 id="常见错误"><a href="#常见错误" class="headerlink" title="常见错误"></a>常见错误</h2><ul><li><code>Error: Another active Homebrew update process is already in progress.</code><br>解决方法：<code>rm -rf /usr/local/var/homebrew/locks</code></li></ul><h2 id="附"><a href="#附" class="headerlink" title="附"></a>附</h2><h3 id="Homebrew常用命令"><a href="#Homebrew常用命令" class="headerlink" title="Homebrew常用命令"></a>Homebrew常用命令</h3><ul><li><p>查看Homebrew版本：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ brew -v</span><br></pre></td></tr></table></figure></li><li><p>Homebrew帮助信息：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ brew [cask] -h</span><br></pre></td></tr></table></figure></li><li><p>更新Homebrew：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ brew update</span><br></pre></td></tr></table></figure></li><li><p>更新Homebrew cask：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ brew cask upgrade</span><br></pre></td></tr></table></figure></li><li><p>安装软件：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ brew [cask] install &lt;packageName&gt;</span><br></pre></td></tr></table></figure></li><li><p>卸载软件：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ brew [cask] uninstall &lt;packageName&gt;</span><br></pre></td></tr></table></figure></li><li><p>查询可用软件：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ brew search &lt;packageName&gt;</span><br></pre></td></tr></table></figure></li><li><p>查看已安装软件：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ brew [cask] list</span><br></pre></td></tr></table></figure></li><li><p>查看软件信息：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ brew [cask] info &lt;packageName&gt;</span><br></pre></td></tr></table></figure></li></ul><h3 id="确认shell版本方式"><a href="#确认shell版本方式" class="headerlink" title="确认shell版本方式"></a>确认shell版本方式</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ echo $SHELL</span><br></pre></td></tr></table></figure><blockquote><p>输出<code>/bin/zsh</code>为<code>zsh</code>终端，输出<code>/bin/bash</code>为<code>bash</code>终端。</p></blockquote>]]></content>
    
    
    <summary type="html">&lt;p&gt;本文将介绍&lt;code&gt;Homebrew&lt;/code&gt;的安装与使用。&lt;/p&gt;</summary>
    
    
    
    <category term="系统配置" scheme="https://answerywj.com/categories/%E7%B3%BB%E7%BB%9F%E9%85%8D%E7%BD%AE/"/>
    
    
    <category term="mac" scheme="https://answerywj.com/tags/mac/"/>
    
    <category term="Homebrew" scheme="https://answerywj.com/tags/Homebrew/"/>
    
  </entry>
  
  <entry>
    <title>Git仓库过大导致clone失败的解决方法</title>
    <link href="https://answerywj.com/2019/09/09/git-clone-extra-large-project/"/>
    <id>https://answerywj.com/2019/09/09/git-clone-extra-large-project/</id>
    <published>2019-09-09T04:00:40.000Z</published>
    <updated>2023-03-20T06:26:35.322Z</updated>
    
    <content type="html"><![CDATA[<p>本文记录工作中遇到的<code>clone</code>大仓库失败的解决过程，以下问题与解决方案均基于<code>https</code>访问。</p><span id="more"></span><h2 id="错误一"><a href="#错误一" class="headerlink" title="错误一"></a>错误一</h2><p>从<code>web</code>端查看仓库大小，大约<code>1.5G</code>左右，首先直接执行<code>git clone</code>，报错如下：</p><figure class="highlight plaintext"><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">remote: Counting objects: 10994, done.</span><br><span class="line">remote: Compressing objects: 100% (3085/3085), done.</span><br><span class="line">error: RPC failed; curl 56 GnuTLS recv error (-110): The TLS connection was non-properly terminated.</span><br><span class="line">fatal: The remote end hung up unexpectedly</span><br><span class="line">fatal: early EOF</span><br><span class="line">fatal: index-pack failed</span><br></pre></td></tr></table></figure><h3 id="增大postBuffer"><a href="#增大postBuffer" class="headerlink" title="增大postBuffer"></a>增大postBuffer</h3><p>在增大<code>postBuffer</code>的同时，关闭<code>ssl</code>认证：</p><figure class="highlight plaintext"><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">$ git config --global http.postBuffer 2048000000 # 设置为2G</span><br><span class="line">$ git config --global http.sslVerify false # 关闭sslVerify</span><br></pre></td></tr></table></figure><p>设置成功后，重新<code>clone</code>，错误依旧。</p><h3 id="使用openssl替换gunssl"><a href="#使用openssl替换gunssl" class="headerlink" title="使用openssl替换gunssl"></a>使用openssl替换gunssl</h3><p>1.安装相关依赖环境：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ sudo apt-get install build-essential fakeroot dpkg-dev libcurl4-openssl-dev</span><br></pre></td></tr></table></figure><p>2.获取git源码：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ sudo apt-get source git</span><br></pre></td></tr></table></figure><p>若出现如下错误：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">E: You must put some &#x27;source&#x27; URIs in your sources.list</span><br></pre></td></tr></table></figure><p>则需要将设置-&gt;<code>Software &amp; Updates</code>-&gt;<code>Ubuntu Software</code>-&gt;<code>Source code</code>勾选：<br><img src="/2019/09/09/git-clone-extra-large-project/source_code.png" alt="source_code"></p><p>若出现如下错误：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">couldn&#x27;t be accessed by user &#x27;_apt&#x27;. - pkgAcquire::Run (13: Permission denied) [duplicate]</span><br></pre></td></tr></table></figure><p>则需要更改权限：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo chown _apt /var/lib/update-notifier/package-data-downloads/partial/</span><br></pre></td></tr></table></figure><p>3.安装<code>git</code>的依赖</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ sudo apt-get build-dep git</span><br></pre></td></tr></table></figure><p>4.进入<code>git</code>目录，重新编译：</p><figure class="highlight plaintext"><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">$ cd git-2.7.4/</span><br><span class="line">$ vim ./debian/control # 将libcurl4-gnutls-dev修改为libcurl4-openssl-dev</span><br><span class="line">$ vim ./debian/rules # 整行删除TEST=test</span><br><span class="line">$ sudo dpkg-buildpackage -rfakeroot -b -uc -us -j4 # 编译</span><br></pre></td></tr></table></figure><p>5.回到上一级目录，安装编译好的安装包：</p><figure class="highlight plaintext"><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">$ cd ..</span><br><span class="line">$ sudo dpkg -i git_2.7.4-0ubuntuxxx_amd64.deb # 安装包名字可能有所不同</span><br></pre></td></tr></table></figure><p>执行完成如上步骤后，重新<code>clone</code>，发现依旧报错，请看错误二。</p><h2 id="错误二"><a href="#错误二" class="headerlink" title="错误二"></a>错误二</h2><figure class="highlight plaintext"><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">remote: Counting objects: 10994, done.</span><br><span class="line">remote: Compressing objects: 100% (3085/3085), done.</span><br><span class="line">error: RPC failed; curl 18 transfer closed with outstanding read data remaining</span><br><span class="line">fatal: The remote end hung up unexpectedly</span><br><span class="line">fatal: early EOF</span><br><span class="line">fatal: index-pack failed</span><br></pre></td></tr></table></figure><p>重新确认<code>postBuffer</code>，配置确实生效了：</p><figure class="highlight plaintext"><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">$ cat ~/.gitconfig</span><br><span class="line"></span><br><span class="line">[http]</span><br><span class="line">sslVerify = false</span><br><span class="line">postBuffer = 2048000000</span><br></pre></td></tr></table></figure><h3 id="浅层clone"><a href="#浅层clone" class="headerlink" title="浅层clone"></a>浅层clone</h3><p>晕，实在搞不定了，采取极端方法，首先<code>clone</code>一层：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git clone --depth=1 http://xxx.git</span><br></pre></td></tr></table></figure><p>浅层<code>clone</code>成功后，再完整拉取：</p><figure class="highlight plaintext"><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">$ git fetch --unshallow # 拉取完整当前分支</span><br><span class="line">$ git remote set-branches origin &#x27;*&#x27; # 追踪所有远程分支</span><br><span class="line">$ git fetch -v # 拉取所有远程分支</span><br></pre></td></tr></table></figure><p>至此，终于成功地<code>clone</code>了一个完整的仓库。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;本文记录工作中遇到的&lt;code&gt;clone&lt;/code&gt;大仓库失败的解决过程，以下问题与解决方案均基于&lt;code&gt;https&lt;/code&gt;访问。&lt;/p&gt;</summary>
    
    
    
    <category term="Git" scheme="https://answerywj.com/categories/Git/"/>
    
    
    <category term="git仓库过大" scheme="https://answerywj.com/tags/git%E4%BB%93%E5%BA%93%E8%BF%87%E5%A4%A7/"/>
    
  </entry>
  
  <entry>
    <title>将二进制文件作为目标文件中的一个段</title>
    <link href="https://answerywj.com/2019/07/24/binary-to-object-file/"/>
    <id>https://answerywj.com/2019/07/24/binary-to-object-file/</id>
    <published>2019-07-24T03:50:23.000Z</published>
    <updated>2023-03-20T06:26:35.314Z</updated>
    
    <content type="html"><![CDATA[<p>本文将展示，如何将一个二进制文件(如图片、音频等)作为目标文件中的一个段，该技巧主要应用在一些无文件系统的平台。</p><span id="more"></span><p>本次的实验场景为<code>i386:x86-64 GNU/Linux</code>，测试音频为<code>nhxc.wav</code>，测试程序为<code>bin2obj.c</code>。</p><h2 id="查看该平台的ELF文件相关信息"><a href="#查看该平台的ELF文件相关信息" class="headerlink" title="查看该平台的ELF文件相关信息"></a>查看该平台的ELF文件相关信息</h2><p>生成目标文件</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ gcc -c bin2obj.c -o bin2obj.o</span><br></pre></td></tr></table></figure><p>查看该平台<code>ELF</code>文件相关信息</p><figure class="highlight plaintext"><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">$ objdump -x bin2obj.o</span><br><span class="line"></span><br><span class="line">bin2obj.o:     file format elf64-x86-64</span><br><span class="line">bin2obj.o</span><br><span class="line">architecture: i386:x86-64, flags 0x00000011:</span><br><span class="line">HAS_RELOC, HAS_SYMS</span><br><span class="line">start address 0x0000000000000000</span><br></pre></td></tr></table></figure><p>由上可知，文件格式为<code>elf64-x86-64</code>，<code>CPU</code>架构为<code>architecture</code>。</p><h2 id="转换"><a href="#转换" class="headerlink" title="转换"></a>转换</h2><p>首先通过<code>objcopy --help</code>选项查看相关参数的意义:</p><figure class="highlight plaintext"><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">$ objcopy --help</span><br><span class="line">-I --input-target &lt;bfdname&gt;      Assume input file is in format &lt;bfdname&gt;</span><br><span class="line">-O --output-target &lt;bfdname&gt;     Create an output file in format &lt;bfdname&gt;</span><br><span class="line">-B --binary-architecture &lt;arch&gt;  Set output arch, when input is arch-less</span><br><span class="line">......</span><br><span class="line">objcopy: supported targets: elf64-x86-64 elf32-i386 elf32-iamcu elf32-x86-64 a.out-i386-linux pei-i386 pei-x86-64 elf64-l1om elf64-k1om elf64-little elf64-big elf32-little elf32-big pe-x86-64 pe-bigobj-x86-64 pe-i386 plugin srec symbolsrec verilog tekhex binary ihex</span><br></pre></td></tr></table></figure><p>由上可知，<code>-I</code>选项指定输入文件的格式，<code>-O</code>指定输出文件的格式，在<code>supported targets</code>中选择对应的格式；-B是指定目标文件的架构<code>i386:x86-64</code>，即上文<code>objdump -x</code>命令查询的<code>architecture</code>。</p><p>转换：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ objcopy -I binary -O elf64-x86-64 -B i386:x86-64 nhxc.wav audio.o</span><br></pre></td></tr></table></figure><p>查看转换后生成的目标文件：</p><figure class="highlight plaintext"><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">$ objdump -x audio.o</span><br><span class="line"></span><br><span class="line">audio.o:     file format elf64-x86-64</span><br><span class="line">audio.o</span><br><span class="line">architecture: i386:x86-64, flags 0x00000010:</span><br><span class="line">HAS_SYMS</span><br><span class="line">start address 0x0000000000000000</span><br><span class="line"></span><br><span class="line">Sections:</span><br><span class="line">Idx Name          Size      VMA               LMA               File off  Algn</span><br><span class="line">  0 .data         0000fab0  0000000000000000  0000000000000000  00000040  2**0</span><br><span class="line">                  CONTENTS, ALLOC, LOAD, DATA</span><br><span class="line">SYMBOL TABLE:</span><br><span class="line">0000000000000000 l    d  .data0000000000000000 .data</span><br><span class="line">0000000000000000 g       .data0000000000000000 _binary_nhxc_wav_start</span><br><span class="line">000000000000fab0 g       .data0000000000000000 _binary_nhxc_wav_end</span><br><span class="line">000000000000fab0 g       *ABS*0000000000000000 _binary_nhxc_wav_size</span><br></pre></td></tr></table></figure><p>可以看到<code>file format</code>、<code>architecture</code>信息与<code>bin2obj.o</code>的相同，<code>_binary_nhxc_wav_start</code>指向音频内容的起始地址，<code>_binary_nhxc_wav_end</code>指向音频内容的结尾地址，<code>_binary_nhxc_wav_size</code>指向文件大小的存储地址。</p><blockquote><p><code>_binary_*_start/end/size</code>，<code>*</code>是二进制文件的文件名及后缀名。</p></blockquote><h2 id="测试"><a href="#测试" class="headerlink" title="测试"></a>测试</h2><p><a href="https://github.com/AnSwErYWJ/DogFood/blob/master/C/bin2obj/bin2obj.c">bin2obj.c</a>：</p><figure class="highlight plaintext"><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></pre></td><td class="code"><pre><span class="line">#include &lt;stdio.h&gt;</span><br><span class="line">#include &lt;elf.h&gt;</span><br><span class="line"></span><br><span class="line">extern _binary_nhxc_wav_start;</span><br><span class="line">extern _binary_nhxc_wav_end;</span><br><span class="line">extern _binary_nhxc_wav_size;</span><br><span class="line"></span><br><span class="line">int main() &#123;</span><br><span class="line">printf(&quot;binary to object:\n&quot;);</span><br><span class="line">    </span><br><span class="line">printf(&quot;elf head: %ld\n&quot;, sizeof(Elf64_Ehdr));</span><br><span class="line">    printf(&quot;_binary_nhxc_wav_size: %p\n_binary_nhxc_wav_end: %p\n_binary_nhxc_wav_size: %p\n&quot;, &amp;_binary_nhxc_wav_start, &amp;_binary_nhxc_wav_end,  &amp;_binary_nhxc_wav_size);</span><br><span class="line"></span><br><span class="line">    unsigned char * audio_buf = (unsigned char *)&amp;_binary_nhxc_wav_start;</span><br><span class="line">    unsigned long size = (unsigned long)&amp;_binary_nhxc_wav_size;</span><br><span class="line"></span><br><span class="line">FILE *fp = fopen(&quot;./out.wav&quot;, &quot;wb&quot;);</span><br><span class="line">if (!fp) &#123;</span><br><span class="line">fprintf(stderr, &quot;fopen failed!\n&quot;);</span><br><span class="line">return -1;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">fwrite(audio_buf, size, 1, fp);</span><br><span class="line"></span><br><span class="line">fclose(fp);</span><br><span class="line"></span><br><span class="line">return 0;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>通过<code>_binary_nhxc_wav_start</code>和<code>_binary_nhxc_wav_size</code>两个符号，读取音频文件。</p><p>编译并运行：</p><figure class="highlight plaintext"><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 -c bin2obj.c -o bin2obj.o</span><br><span class="line">$ g++ bin2obj.o audio.o -o bin2obj</span><br><span class="line">$ ./bin2obj</span><br><span class="line">binary to object:</span><br><span class="line">elf head: 64</span><br><span class="line">_binary_nhxc_wav_size: 0x601040</span><br><span class="line">_binary_nhxc_wav_end: 0x610af0</span><br><span class="line">_binary_nhxc_wav_size: 0xfab0</span><br></pre></td></tr></table></figure><p>比对写入的文件<code>out.wav</code>与原始文件<code>nhxc.wav</code>，完全一致：</p><figure class="highlight plaintext"><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">155e62d81e84fa7493fefe82223bcc2a  nhxc.wav</span><br><span class="line">155e62d81e84fa7493fefe82223bcc2a  out.wav</span><br></pre></td></tr></table></figure><p>查看audio.o：</p><figure class="highlight plaintext"><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">$ hexdump -C audio.o | head -n 5</span><br><span class="line">00000000  7f 45 4c 46 02 01 01 00  00 00 00 00 00 00 00 00  |.ELF............|</span><br><span class="line">00000010  01 00 3e 00 01 00 00 00  00 00 00 00 00 00 00 00  |..&gt;.............|</span><br><span class="line">00000020  00 00 00 00 00 00 00 00  d0 fb 00 00 00 00 00 00  |................|</span><br><span class="line">00000030  00 00 00 00 40 00 00 00  00 00 40 00 05 00 02 00  |....@.....@.....|</span><br><span class="line">00000040  52 49 46 46 a8 fa 00 00  57 41 56 45 66 6d 74 20  |RIFF....WAVEfmt |</span><br></pre></td></tr></table></figure><p>如程序输出，<code>ELF</code>文件头部信息结构体为64字节，而转换生成的目标文件中，音频内容始于<code>0x40</code>字节偏移（<code>wav</code>头始于<code>RIFF</code>，可以参考<a href="http://answerywj.com/2019/06/03/wav/">wav文件解析</a>），而<code>0x40</code>正是十进制的<code>64</code>。</p><h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><ul><li>《程序员的自我修养——链接、装载与库》P68</li></ul>]]></content>
    
    
    <summary type="html">&lt;p&gt;本文将展示，如何将一个二进制文件(如图片、音频等)作为目标文件中的一个段，该技巧主要应用在一些无文件系统的平台。&lt;/p&gt;</summary>
    
    
    
    <category term="编译链接" scheme="https://answerywj.com/categories/%E7%BC%96%E8%AF%91%E9%93%BE%E6%8E%A5/"/>
    
    
    <category term="objdump" scheme="https://answerywj.com/tags/objdump/"/>
    
    <category term="elf" scheme="https://answerywj.com/tags/elf/"/>
    
  </entry>
  
  <entry>
    <title>深究strtok系列函数</title>
    <link href="https://answerywj.com/2019/05/21/man-strtok/"/>
    <id>https://answerywj.com/2019/05/21/man-strtok/</id>
    <published>2019-05-21T07:59:44.000Z</published>
    <updated>2023-03-20T06:26:35.334Z</updated>
    
    <content type="html"><![CDATA[<p>本文通过分析源码，深究<code>GLIBC</code>中<code>strtok</code>和<code>strtok_r</code>函数的实现原理和使用过程中的注意事项。</p><span id="more"></span><h2 id="函数说明"><a href="#函数说明" class="headerlink" title="函数说明"></a>函数说明</h2><figure class="highlight plaintext"><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">#include &lt;string.h&gt;</span><br><span class="line"></span><br><span class="line">char *strtok(char *str, const char *delim);</span><br><span class="line">char *strtok_r(char *str, const char *delim, char **saveptr);</span><br></pre></td></tr></table></figure><h3 id="说明"><a href="#说明" class="headerlink" title="说明"></a>说明</h3><ul><li><code>strtok</code>以包含在<code>delim</code>中的字符为分割符，将<code>str</code>分割成一个个子串；若<code>str</code>为空值<code>NULL</code>，则函数内部保存的静态指针（指向上一次分割位置后一个字节）在下一次调用中将作为起始位置。</li><li><code>strtok_r</code>功能同<code>strtok</code>，不过其将<code>strtok</code>函数内部保存的指针显示化，通过<code>saveptr</code>输入，以<code>saveptr</code>作为分割的起始位置。</li></ul><h3 id="参数"><a href="#参数" class="headerlink" title="参数"></a>参数</h3><ul><li><code>str</code>: 待分割的源字符串</li><li><code>delim</code>: 分割符字符集合</li><li><code>saveptr</code>: 一个指向<code>char *</code>的指针变量，保存分割时的上下文</li></ul><h3 id="返回值"><a href="#返回值" class="headerlink" title="返回值"></a>返回值</h3><ul><li>若未提取到子串，返回值为指向源字符串首地址的指针，可以完整打印源字符串</li><li>若提取到子串，返回值为提取出的子串的指针，这个指针指向的是子串在源字符串中的起始位置，因为子串末尾的下一个字符在提取前为分割符，提取后被修改成了<code>&#39;/0’</code>，所以可以成功打印子串的内容</li><li>若在成功提取到子串后，没有可以被分割的子串，返回NULL</li></ul><h2 id="示例"><a href="#示例" class="headerlink" title="示例"></a>示例</h2><figure class="highlight plaintext"><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">#include &lt;stdio.h&gt;</span><br><span class="line">#include &lt;string.h&gt;</span><br><span class="line"></span><br><span class="line">int main(void) &#123;</span><br><span class="line">  char str[12] = &quot;hello,world\0&quot;;</span><br><span class="line">  char *token = strtok(str, &quot;,&quot;);</span><br><span class="line"></span><br><span class="line">  while (token != NULL) &#123;</span><br><span class="line">    printf(&quot;%s\n&quot;, token);</span><br><span class="line">    token = strtok(NULL, &quot;,&quot;);</span><br><span class="line">  &#125;</span><br><span class="line">   </span><br><span class="line">  return 0;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="使用注意事项"><a href="#使用注意事项" class="headerlink" title="使用注意事项"></a>使用注意事项</h2><h3 id="不会生成新的字符串，只是在源字符串上做了修改，源字符串会发生变化"><a href="#不会生成新的字符串，只是在源字符串上做了修改，源字符串会发生变化" class="headerlink" title="不会生成新的字符串，只是在源字符串上做了修改，源字符串会发生变化"></a>不会生成新的字符串，只是在源字符串上做了修改，源字符串会发生变化</h3><figure class="highlight plaintext"><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">char str[12] = &quot;hello,world\0&quot;;</span><br><span class="line">printf(&quot;str before strtok: %s\n&quot;, str);</span><br><span class="line">char *token = strtok(str, &quot;,&quot;);</span><br><span class="line">printf(&quot;str after strtok: %s\n&quot;, str);</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><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">$ str before strtok: hello,world</span><br><span class="line">$ str after strtok: hello</span><br></pre></td></tr></table></figure><p>如上实验，<code>str</code>的值，在对其做<code>strtok</code>操作之后，发生了变化，分割符之后的内容不见了。事实上，<code>strtok</code>函数是根据输入的分割符（即<code>,</code>），找到其首次出现的位置（即<code>world</code>之前的<code>,</code>），将其修改为<code>&#39;/0’</code>。</p><h3 id="第一个参数不可为字符串常量"><a href="#第一个参数不可为字符串常量" class="headerlink" title="第一个参数不可为字符串常量"></a>第一个参数不可为字符串常量</h3><p>因为<code>strtok</code>函数会修改源字符串，所以第一个参数不可为字符串常量，不然程序会抛出异常。</p><h3 id="若在第一次提取子串后，继续对源字符串进行提取，应在其后的调用中将第一个参数置为空值NULL"><a href="#若在第一次提取子串后，继续对源字符串进行提取，应在其后的调用中将第一个参数置为空值NULL" class="headerlink" title="若在第一次提取子串后，继续对源字符串进行提取，应在其后的调用中将第一个参数置为空值NULL"></a>若在第一次提取子串后，继续对源字符串进行提取，应在其后的调用中将第一个参数置为空值<code>NULL</code></h3><figure class="highlight plaintext"><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">char str[12] = &quot;hello,world\0&quot;;</span><br><span class="line">char *token = strtok(str, &quot;,&quot;);   </span><br><span class="line">while (token != NULL) &#123;</span><br><span class="line">    printf(&quot;%s\n&quot;, token);</span><br><span class="line">    token = strtok(NULL, &quot;,&quot;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><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">$ hello</span><br><span class="line">$ world</span><br></pre></td></tr></table></figure><p>在第一次提取子串时，<code>strtok</code>用一个指针指向了分割符的下一位，即’w’所在的位置，后续的提取给<code>strtok</code>的第一个参数传递了空值<code>NULL</code>，<code>strtok</code>会从上一次调用隐式保存的位置，继续分割字符串。</p><h3 id="第二个参数是分割符的集合，支持多个分割符"><a href="#第二个参数是分割符的集合，支持多个分割符" class="headerlink" title="第二个参数是分割符的集合，支持多个分割符"></a>第二个参数是分割符的集合，支持多个分割符</h3><figure class="highlight plaintext"><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">char str[12] = &quot;hello,world\0&quot;;</span><br><span class="line">char *token = strtok(str, &quot;,l&quot;);</span><br><span class="line">printf(&quot;%s\n&quot;, token);</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ he</span><br></pre></td></tr></table></figure><p>由上可见，<code>strtok</code>函数在分割字符串时，不是完整匹配第二个参数传入的分割符，而是使用包含在分割符集合中的字符进行匹配。</p><h3 id="若首字符为分割符，则会被忽略"><a href="#若首字符为分割符，则会被忽略" class="headerlink" title="若首字符为分割符，则会被忽略"></a>若首字符为分割符，则会被忽略</h3><figure class="highlight plaintext"><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">char str[13] = &quot;,hello,world\0&quot;;</span><br><span class="line">char *token = strtok(str, &quot;,&quot;);</span><br><span class="line">printf(&quot;%s\n&quot;, token);</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hello</span><br></pre></td></tr></table></figure><p>如上所示，若首字符为分割符，<code>strtok</code>采用了比常规处理更快的方式，直接跳过了首字符。</p><h3 id="strtok为不可重入函数，使用strtok-r更灵活和安全"><a href="#strtok为不可重入函数，使用strtok-r更灵活和安全" class="headerlink" title="strtok为不可重入函数，使用strtok_r更灵活和安全"></a><code>strtok</code>为不可重入函数，使用<code>strtok_r</code>更灵活和安全</h3><p><code>strtok</code>函数在内部使用了静态变量，即用静态指针保存了下一次调用的起始位置，对调用者不可见；<code>strtok_r</code>则将<code>strtok</code>内部隐式保存的指针，以参数的形式由调用者进行传递、保存甚至是修改，使函数更具灵活性和安全性；此外，在<code>windows</code>也有分割字符串安全函数<code>strtok_s</code>。</p><h2 id="源码"><a href="#源码" class="headerlink" title="源码"></a>源码</h2><p>strtok.c:</p><figure class="highlight plaintext"><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></pre></td><td class="code"><pre><span class="line">/* Copyright (C) 1991-2018 Free Software Foundation, Inc.</span><br><span class="line">   This file is part of the GNU C Library.</span><br><span class="line"></span><br><span class="line">   The GNU C Library is free software; you can redistribute it and/or</span><br><span class="line">   modify it under the terms of the GNU Lesser General Public</span><br><span class="line">   License as published by the Free Software Foundation; either</span><br><span class="line">   version 2.1 of the License, or (at your option) any later version.</span><br><span class="line"></span><br><span class="line">   The GNU C Library is distributed in the hope that it will be useful,</span><br><span class="line">   but WITHOUT ANY WARRANTY; without even the implied warranty of</span><br><span class="line">   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU</span><br><span class="line">   Lesser General Public License for more details.</span><br><span class="line"></span><br><span class="line">   You should have received a copy of the GNU Lesser General Public</span><br><span class="line">   License along with the GNU C Library; if not, see</span><br><span class="line">   &lt;http://www.gnu.org/licenses/&gt;.  */</span><br><span class="line"></span><br><span class="line">#include &lt;string.h&gt;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">/* Parse S into tokens separated by characters in DELIM.</span><br><span class="line">   If S is NULL, the last string strtok() was called with is</span><br><span class="line">   used.  For example:</span><br><span class="line">char s[] = &quot;-abc-=-def&quot;;</span><br><span class="line">x = strtok(s, &quot;-&quot;);// x = &quot;abc&quot;</span><br><span class="line">x = strtok(NULL, &quot;-=&quot;);// x = &quot;def&quot;</span><br><span class="line">x = strtok(NULL, &quot;=&quot;);// x = NULL</span><br><span class="line">// s = &quot;abc\0=-def\0&quot;</span><br><span class="line">*/</span><br><span class="line">char *</span><br><span class="line">strtok (char *s, const char *delim)</span><br><span class="line">&#123;</span><br><span class="line">  static char *olds;</span><br><span class="line">  return __strtok_r (s, delim, &amp;olds);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>strtok_r.c:</p><figure class="highlight plaintext"><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></pre></td><td class="code"><pre><span class="line">/* Reentrant string tokenizer.  Generic version.</span><br><span class="line">   Copyright (C) 1991-2018 Free Software Foundation, Inc.</span><br><span class="line">   This file is part of the GNU C Library.</span><br><span class="line"></span><br><span class="line">   The GNU C Library is free software; you can redistribute it and/or</span><br><span class="line">   modify it under the terms of the GNU Lesser General Public</span><br><span class="line">   License as published by the Free Software Foundation; either</span><br><span class="line">   version 2.1 of the License, or (at your option) any later version.</span><br><span class="line"></span><br><span class="line">   The GNU C Library is distributed in the hope that it will be useful,</span><br><span class="line">   but WITHOUT ANY WARRANTY; without even the implied warranty of</span><br><span class="line">   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU</span><br><span class="line">   Lesser General Public License for more details.</span><br><span class="line"></span><br><span class="line">   You should have received a copy of the GNU Lesser General Public</span><br><span class="line">   License along with the GNU C Library; if not, see</span><br><span class="line">   &lt;http://www.gnu.org/licenses/&gt;.  */</span><br><span class="line"></span><br><span class="line">#ifdef HAVE_CONFIG_H</span><br><span class="line"># include &lt;config.h&gt;</span><br><span class="line">#endif</span><br><span class="line"></span><br><span class="line">#include &lt;string.h&gt;</span><br><span class="line"></span><br><span class="line">#ifndef _LIBC</span><br><span class="line">/* Get specification.  */</span><br><span class="line"># include &quot;strtok_r.h&quot;</span><br><span class="line"># define __strtok_r strtok_r</span><br><span class="line">#endif</span><br><span class="line"></span><br><span class="line">/* Parse S into tokens separated by characters in DELIM.</span><br><span class="line">   If S is NULL, the saved pointer in SAVE_PTR is used as</span><br><span class="line">   the next starting point.  For example:</span><br><span class="line">char s[] = &quot;-abc-=-def&quot;;</span><br><span class="line">char *sp;</span><br><span class="line">x = strtok_r(s, &quot;-&quot;, &amp;sp);// x = &quot;abc&quot;, sp = &quot;=-def&quot;</span><br><span class="line">x = strtok_r(NULL, &quot;-=&quot;, &amp;sp);// x = &quot;def&quot;, sp = NULL</span><br><span class="line">x = strtok_r(NULL, &quot;=&quot;, &amp;sp);// x = NULL</span><br><span class="line">// s = &quot;abc\0-def\0&quot;</span><br><span class="line">*/</span><br><span class="line">char *</span><br><span class="line">__strtok_r (char *s, const char *delim, char **save_ptr)</span><br><span class="line">&#123;</span><br><span class="line">  char *end;</span><br><span class="line"></span><br><span class="line">  if (s == NULL)</span><br><span class="line">    s = *save_ptr;</span><br><span class="line"></span><br><span class="line">  if (*s == &#x27;\0&#x27;)</span><br><span class="line">    &#123;</span><br><span class="line">      *save_ptr = s;</span><br><span class="line">      return NULL;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">  /* Scan leading delimiters.  */</span><br><span class="line">  s += strspn (s, delim);</span><br><span class="line">  if (*s == &#x27;\0&#x27;)</span><br><span class="line">    &#123;</span><br><span class="line">      *save_ptr = s;</span><br><span class="line">      return NULL;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">  /* Find the end of the token.  */</span><br><span class="line">  end = s + strcspn (s, delim);</span><br><span class="line">  if (*end == &#x27;\0&#x27;)</span><br><span class="line">    &#123;</span><br><span class="line">      *save_ptr = end;</span><br><span class="line">      return s;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">  /* Terminate the token and make *SAVE_PTR point past it.  */</span><br><span class="line">  *end = &#x27;\0&#x27;;</span><br><span class="line">  *save_ptr = end + 1;</span><br><span class="line">  return s;</span><br><span class="line">&#125;</span><br><span class="line">#ifdef weak_alias</span><br><span class="line">libc_hidden_def (__strtok_r)</span><br><span class="line">weak_alias (__strtok_r, strtok_r)</span><br><span class="line">#endif</span><br></pre></td></tr></table></figure><h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><ul><li><a href="https://vimsky.com/article/3185.html">C语言线程安全:不可重入函数汇总</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;p&gt;本文通过分析源码，深究&lt;code&gt;GLIBC&lt;/code&gt;中&lt;code&gt;strtok&lt;/code&gt;和&lt;code&gt;strtok_r&lt;/code&gt;函数的实现原理和使用过程中的注意事项。&lt;/p&gt;</summary>
    
    
    
    <category term="C/C++" scheme="https://answerywj.com/categories/C-C/"/>
    
    
    <category term="strtok" scheme="https://answerywj.com/tags/strtok/"/>
    
    <category term="strtok_r" scheme="https://answerywj.com/tags/strtok-r/"/>
    
  </entry>
  
  <entry>
    <title>sysroot为何物?</title>
    <link href="https://answerywj.com/2019/04/26/what-is-sysroot/"/>
    <id>https://answerywj.com/2019/04/26/what-is-sysroot/</id>
    <published>2019-04-26T09:59:57.000Z</published>
    <updated>2023-03-20T06:26:35.342Z</updated>
    
    <content type="html"><![CDATA[<p>本文介绍链接过程中<code>sysroot</code>的作用。</p><span id="more"></span><h2 id="sysroot为何物"><a href="#sysroot为何物" class="headerlink" title="sysroot为何物"></a>sysroot为何物</h2><p>做过交叉编译的同学们，一定对下面这个错误十分熟悉吧：</p><figure class="highlight plaintext"><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">/cross-compiling/ld: cannot find crt1.o: No such file or directory</span><br><span class="line">/cross-compiling/ld: cannot find crti.o: No such file or directory</span><br></pre></td></tr></table></figure><p>在我们的<code>pc</code>上，这两个文件一般在<code>/usr/lib</code>或者<code>/usr/lib32</code>中，通过<code>gcc -print-search-dirs</code>可以看到这两个路径默认就在库的搜索路径中，所以在<code>pc</code>上编译程序时不存在链接器找不到<code>crt1.o</code>和<code>crti.o</code>的问题。</p><blockquote><p><code>crt1.o</code>负责应用程序的启动，其中包含了程序的入口函数<code>_start</code>以及两个未定义的符号<code>__libc_start_main</code>和<code>main</code>，由<code>_start</code>负责调用<code>__libc_start_main</code>初始化<code>libc</code>，然后调用我们源代码中定义的<code>main</code>函数，<code>crti.o</code>负责辅助启动这些代码。</p></blockquote><p>下面我们使用交叉编译工具链来查看库的搜索路径<code>/cross-compiling/gcc -print-search-dirs</code>，发现<code>crt1.o</code>和<code>crti.o</code>的所在目录并不在库的搜索路径中，所以会出现上述的问题。</p><p>下面就需要<code>sysroot</code>出场了。<br><code>sysroot</code>被称为逻辑根目录，只在链接过程中起作用，作为交叉编译工具链搜索库文件的根路径，如配置<code>--sysroot=dir</code>，则<code>dir</code>作为逻辑根目录，链接器将在<code>dir/usr/lib</code>中搜索库文件。</p><blockquote><p>只有链接器开启了–with-sysroot选项，–sysroot&#x3D;director才生效</p></blockquote><h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><ul><li><a href="https://stackoverflow.com/questions/91576/crti-o-file-missing">crti.o file missing</a></li><li><a href="https://blog.csdn.net/farmwang/article/details/73195951">crt1.o, crti.o, crtbegin.o, crtend.o, crtn.o</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;p&gt;本文介绍链接过程中&lt;code&gt;sysroot&lt;/code&gt;的作用。&lt;/p&gt;</summary>
    
    
    
    <category term="编译链接" scheme="https://answerywj.com/categories/%E7%BC%96%E8%AF%91%E9%93%BE%E6%8E%A5/"/>
    
    
    <category term="ld" scheme="https://answerywj.com/tags/ld/"/>
    
    <category term="sysroot" scheme="https://answerywj.com/tags/sysroot/"/>
    
  </entry>
  
  <entry>
    <title>屏蔽静态库接口</title>
    <link href="https://answerywj.com/2019/04/13/hide-symbol-of-static-library/"/>
    <id>https://answerywj.com/2019/04/13/hide-symbol-of-static-library/</id>
    <published>2019-04-13T06:43:13.000Z</published>
    <updated>2023-03-20T06:26:35.322Z</updated>
    
    <content type="html"><![CDATA[<p>分享屏蔽静态库接口的一种方法.</p><span id="more"></span><h2 id="准备"><a href="#准备" class="headerlink" title="准备"></a>准备</h2><p><code>hello.c</code>:</p><figure class="highlight plaintext"><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">#include &lt;stdio.h&gt;</span><br><span class="line"></span><br><span class="line">__attribute__ ((visibility (&quot;default&quot;))) void hello() &#123;</span><br><span class="line">printf(&quot;Hello World!\n&quot;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>hello.h</code>:</p><figure class="highlight plaintext"><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">#ifndef __HELLO__H</span><br><span class="line">#define __HELLO__H</span><br><span class="line"></span><br><span class="line">#ifdef __cplusplus</span><br><span class="line">extern &quot;C&quot; &#123;</span><br><span class="line">#endif</span><br><span class="line"></span><br><span class="line">void hello();</span><br><span class="line"></span><br><span class="line">#ifdef __cplusplus</span><br><span class="line">&#125;</span><br><span class="line">#endif</span><br><span class="line"></span><br><span class="line">#endif</span><br></pre></td></tr></table></figure><p><code>bye.c</code>:</p><figure class="highlight plaintext"><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">#include &lt;stdio.h&gt;</span><br><span class="line"></span><br><span class="line">void bye() &#123;</span><br><span class="line">printf(&quot;Bye Bye!\n&quot;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>bye.h</code>:</p><figure class="highlight plaintext"><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">#ifndef __BYE__H</span><br><span class="line">#define __BYE__H</span><br><span class="line"></span><br><span class="line">#ifdef __cplusplus</span><br><span class="line">extern &quot;C&quot; &#123;</span><br><span class="line">#endif</span><br><span class="line"></span><br><span class="line">void bye();</span><br><span class="line"></span><br><span class="line">#ifdef __cplusplus</span><br><span class="line">&#125;</span><br><span class="line">#endif</span><br><span class="line"></span><br><span class="line">#endif</span><br></pre></td></tr></table></figure><h2 id="编译"><a href="#编译" class="headerlink" title="编译"></a>编译</h2><p>编译时使用<code>-fvisibility=hidden</code>,可以默认将符号隐藏;需要对外的符号使用<code>__attribute__ ((visibility (&quot;default&quot;)))</code>修饰即可:</p><figure class="highlight plaintext"><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 -fvisibility=hidden -I. -c hello.c -o hello.o</span><br><span class="line">$ gcc -fvisibility=hidden -I. -c bye.c -o bye.o</span><br></pre></td></tr></table></figure><p>其中<code>hello()</code>未被隐藏,<code>bye()</code>是被隐藏的.</p><h2 id="链接"><a href="#链接" class="headerlink" title="链接"></a>链接</h2><p>将生成的两个<code>.o</code>文件重定位到<code>libt.o</code>中:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ ld -r hello.o bye.o -o libt.o</span><br></pre></td></tr></table></figure><h2 id="去除无用的符号"><a href="#去除无用的符号" class="headerlink" title="去除无用的符号"></a>去除无用的符号</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ strip --strip-unneeded libt.o</span><br></pre></td></tr></table></figure><h2 id="隐藏的符号本地化-我也不知道中文怎么翻译了"><a href="#隐藏的符号本地化-我也不知道中文怎么翻译了" class="headerlink" title="隐藏的符号本地化(我也不知道中文怎么翻译了)"></a>隐藏的符号本地化(我也不知道中文怎么翻译了)</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ objcopy --localize-hidden libt.o libt_hidden.o</span><br></pre></td></tr></table></figure><h2 id="打包成静态库"><a href="#打包成静态库" class="headerlink" title="打包成静态库"></a>打包成静态库</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ ar crv libt.a libt_hidden.o</span><br></pre></td></tr></table></figure><h2 id="验证"><a href="#验证" class="headerlink" title="验证"></a>验证</h2><h3 id="调用未被隐藏的hello"><a href="#调用未被隐藏的hello" class="headerlink" title="调用未被隐藏的hello()"></a>调用未被隐藏的<code>hello()</code></h3><p><code>test1.c</code>:</p><figure class="highlight plaintext"><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">#include &quot;hello.h&quot;</span><br><span class="line"></span><br><span class="line">int main(void) &#123;</span><br><span class="line">    hello();</span><br><span class="line">    return 0;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>编译并运行</p><figure class="highlight plaintext"><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 -I. test1.c -L. -lt -o test</span><br><span class="line">$ ./test</span><br><span class="line">Hello World!</span><br></pre></td></tr></table></figure><h3 id="调用隐藏的bye"><a href="#调用隐藏的bye" class="headerlink" title="调用隐藏的bye()"></a>调用隐藏的<code>bye()</code></h3><p>test2.c</p><figure class="highlight plaintext"><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">#include &quot;bye.h&quot;</span><br><span class="line"></span><br><span class="line">int main(void) &#123;</span><br><span class="line">    bye();</span><br><span class="line">    return 0;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>编译并运行</p><figure class="highlight plaintext"><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 -I. test2.c -L. -lt -o test</span><br><span class="line">$ ./test</span><br><span class="line">/tmp/ccdaJT7s.o: In function `main&#x27;:</span><br><span class="line">test2.c:(.text+0xa): undefined reference to `bye&#x27;</span><br><span class="line">collect2: error: ld returned 1 exit status</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">&lt;p&gt;分享屏蔽静态库接口的一种方法.&lt;/p&gt;</summary>
    
    
    
    <category term="编译链接" scheme="https://answerywj.com/categories/%E7%BC%96%E8%AF%91%E9%93%BE%E6%8E%A5/"/>
    
    
    <category term="hidden" scheme="https://answerywj.com/tags/hidden/"/>
    
    <category term="objcopy" scheme="https://answerywj.com/tags/objcopy/"/>
    
  </entry>
  
  <entry>
    <title>Git速查手册（第二版）</title>
    <link href="https://answerywj.com/2019/02/12/git-help-v2/"/>
    <id>https://answerywj.com/2019/02/12/git-help-v2/</id>
    <published>2019-02-12T04:02:40.000Z</published>
    <updated>2023-03-20T06:26:35.322Z</updated>
    
    <content type="html"><![CDATA[<p>本文是对之前<a href="http://answerywj.com/2016/08/28/Git%E9%80%9F%E6%9F%A5%E6%89%8B%E5%86%8C/">Git速查手册</a>的更新，增加了一些这段时间使用到的命令。</p><span id="more"></span><h2 id="配置git"><a href="#配置git" class="headerlink" title="配置git"></a>配置git</h2><p>笔者使用的是v2.1.0，推荐大家使用v1.8以上的<a href="https://git-scm.com/downloads">版本</a>。 查看git版本：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git --version</span><br></pre></td></tr></table></figure><p>配置命令<code>git config</code>分为三个级别：</p><figure class="highlight plaintext"><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">--system : 系统级,位于 /etc/gitconfig .</span><br><span class="line">--global : 用户级,位于 ~/.gitconfig .</span><br><span class="line">--local : 仓库级,位于 repo/.git/config ,default并且优先级最高.</span><br></pre></td></tr></table></figure><p>首先需要删除global用户信息,防止不同git软件之间的冲突：</p><figure class="highlight plaintext"><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">$ git config --global --unset user.name</span><br><span class="line">$ git config --global --unset user.email</span><br></pre></td></tr></table></figure><p>设置用户信息.若同时使用gitlab和github,推荐配置local用户信息：</p><figure class="highlight plaintext"><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">$ git config --local user.name &quot;username&quot;</span><br><span class="line">$ git config --local user.email &quot;email&quot;</span><br></pre></td></tr></table></figure><p>git支持https和ssh等协议.https除了速度慢以外，还有个最大的麻烦是每次推送都必须输入口令，而ssh支持的原生git协议速度最快。<br>检查本机SSH公钥:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ ls ~/.ssh</span><br></pre></td></tr></table></figure><p>若存在,则将<code>id_rsa.pub</code>添加到github的SSH keys中。若不存在,则生成:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ ssh-keygen -t rsa -C &quot;your_email@youremail.com&quot; </span><br></pre></td></tr></table></figure><p>当ssh配置完成后，再次检查ssh连接情况：</p><figure class="highlight plaintext"><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">$ ssh -T git@github.com</span><br><span class="line">Hi! You’ve successfully authenticated, but GitHub does not provide shell access.</span><br></pre></td></tr></table></figure><p>若出现上述信息，则表示设置成功。<br>若使用https访问, 则进行如下配置,并且设置超时时间避免重复输入密码：</p><figure class="highlight plaintext"><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">$ git config --global http.sslverify false</span><br><span class="line">$ git config --global credential.helper &#x27;cache --timeout=3600&#x27;</span><br></pre></td></tr></table></figure><p>设置可视化diff和merge工具, linux系统上推荐使用meld或者diffuse：</p><figure class="highlight plaintext"><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">$ git config --global diff.tool meld</span><br><span class="line">$ git config --global merge.tool meld</span><br></pre></td></tr></table></figure><p>保存用户名,密码, 避免每次<code>pull/push</code>操作都需要手动输入：</p><figure class="highlight plaintext"><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">$ git config --global credential.helper store</span><br><span class="line"># 执行上免的命令后, 下次操作输入的密码会被保存</span><br></pre></td></tr></table></figure><p>设置颜色,利于使用：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git config --global color.ui.true</span><br></pre></td></tr></table></figure><p>设置别名：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git config --global alias checkout co </span><br></pre></td></tr></table></figure><blockquote><p>上面的命令将<code>checkout</code>设置为别名<code>co</code>。</p></blockquote><p>最后,查看一下所有的设置：</p><figure class="highlight plaintext"><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">$ git config --local --list</span><br><span class="line">$ git config --global --list</span><br><span class="line">$ git config --system --list</span><br></pre></td></tr></table></figure><h2 id="工作流"><a href="#工作流" class="headerlink" title="工作流"></a>工作流</h2><p><img src="/2019/02/12/git-help-v2/git-work-flow.jpg" alt="工作流"></p><p>工作区就是你的本地仓库文件夹,不过其中的<code>.git</code>目录不属于工作区,而是版本库。里面存了很多东西，其中最重要的就是称为stage（或者叫index）的暂存区，还有Git为我们自动创建的第一个分支master，以及指向master的一个指针叫HEAD。<br>现在来解释一下前面的添加和提交操作：  </p><ol><li><code>git add</code>把文件添加进去，实际上就是把文件修改添加到暂存区；</li><li><code>git commit</code>提交更改，实际上就是把暂存区的所有内容提交到当前分支。<br>因为我们创建Git版本库时，Git自动为我们创建了唯一一个master分支，所以，现在，git commit就是往master分支上提交更改。</li></ol><h2 id="基本操作"><a href="#基本操作" class="headerlink" title="基本操作"></a>基本操作</h2><p>获取远程仓库：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git clone git@github.com:USERNAME/repo.git</span><br></pre></td></tr></table></figure><p>将本地的仓库添加到远程：</p><figure class="highlight plaintext"><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">$ cd repo</span><br><span class="line">$ git init</span><br><span class="line">$ git remote add origin git@github.com:USERNAME/repo.git</span><br></pre></td></tr></table></figure><blockquote><p><code>origin</code>就是一个名字，是<code>git</code>为你默认创建的指向这个远程代码库的标签。</p></blockquote><p>添加修改:</p><figure class="highlight plaintext"><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">$ git add &lt;filename&gt; </span><br><span class="line">$ git add .  # 添加当前目录所有修改过的文件  </span><br><span class="line">$ git add *  # 递归地添加执行命令时所在的目录中的所有文件</span><br></pre></td></tr></table></figure><p>提交修改:</p><figure class="highlight plaintext"><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">$ git commit -m &quot;commit message&quot;</span><br><span class="line">$ git commit -am &quot;commit message&quot;</span><br></pre></td></tr></table></figure><blockquote><p><code>commit message</code>的填写可以参考<a href="http://blog.jobbole.com/92713/">写好 Git Commit 信息的 7 个建议</a>。<br><code>am</code>将添加和提交合并为一步,但只对本来就存在的文件有效。</p></blockquote><p>推送修改：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git push -u origin &lt;feature-branch-name&gt;</span><br></pre></td></tr></table></figure><blockquote><p><code>-u</code>选项可以将本地分支与远程分支关联,下次<code>git pull</code>操作时可以不带参数.具体参见<a href="http://stackoverflow.com/questions/5697750/what-exactly-does-the-u-do-git-push-u-origin-master-vs-git-push-origin-ma">这里</a>。</p></blockquote><p>查看远程仓库：</p><figure class="highlight plaintext"><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">$ git remote -v</span><br><span class="line">origin git@github.com:USERNAME/repo.git (push)</span><br><span class="line">origin git@github.com:USERNAME/repo.git (fetch)</span><br></pre></td></tr></table></figure><p>fork后同步上游仓库的更新：</p><figure class="highlight plaintext"><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><br><span class="line">$ git remote add upstream git@github.com:USERNAME/repo.git</span><br><span class="line"> </span><br><span class="line">$ git remote -v</span><br><span class="line">origin  git@github.com:USERNAME/repo.git (push)</span><br><span class="line">origin  git@github.com:USERNAME/repo.git (fetch)</span><br><span class="line">upstream  git@github.com:USERNAME/repo.git  (push)</span><br><span class="line">upstream  git@github.com:USERNAME/repo.git (fetch)</span><br><span class="line"></span><br><span class="line">$ git fetch upstream </span><br><span class="line">$ git difftool &lt;branch-name&gt; upstream/master</span><br><span class="line">$ git merge upstream/master</span><br><span class="line">$ git mergetool</span><br></pre></td></tr></table></figure><p>引用公共代码：<br>代码引用在git上有两种方式：<code>submodule</code>和<code>subtree</code>，推荐使用<a href="http://aoxuis.me/post/2013-08-06-git-subtree">subtree</a>方式。</p><figure class="highlight plaintext"><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><br><span class="line">$ git remote add -f &lt;remote-subtree-repository-name&gt; &lt;remote-subtree-repository-url&gt;</span><br><span class="line">$ git subtree add --prefix=&lt;local-subtree-directory&gt; &lt;remote-subtree-repository&gt; &lt;remote-subtree-branch-name&gt; --squash</span><br><span class="line"></span><br><span class="line"># 同步subtree的更新</span><br><span class="line">$ git subtree pull --prefix=&lt;local-subtree-directory&gt; &lt;remote-subtree-repository&gt; &lt;remote-subtree-branch-name&gt; --squash</span><br><span class="line"></span><br><span class="line"># 推送到远程subtree库</span><br><span class="line">$ git subtree push --prefix=&lt;local-subtree-directory&gt; &lt;remote-subtree-repository&gt; &lt;remote-subtree-branch-name&gt;</span><br></pre></td></tr></table></figure><h2 id="使用标签"><a href="#使用标签" class="headerlink" title="使用标签"></a>使用标签</h2><p>查看标签 ：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git tag</span><br></pre></td></tr></table></figure><p>创建标签 ：</p><figure class="highlight plaintext"><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">$ git tag -a &lt;tagname&gt; -m &quot;tag message&quot; # 创建标签在当前最新提交的commit上</span><br><span class="line">$ git tag -a &lt;tagname&gt; -m &quot;tag message&quot; &lt;commit id&gt; # 创建标签在指定的commit上</span><br></pre></td></tr></table></figure><p>推送标签到远程：</p><figure class="highlight plaintext"><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">$ git push origin &lt;tagname&gt; # 推送一个本地标签</span><br><span class="line">$ git push origin --tags # 推送全部未推送过的本地标签</span><br></pre></td></tr></table></figure><p>删除标签：</p><figure class="highlight plaintext"><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">$ git tag -d &lt;tagname&gt; # 删除一个本地标签；</span><br><span class="line">$ git push origin :refs/tags/&lt;tagname&gt; # 删除一个远程标签。</span><br></pre></td></tr></table></figure><h2 id="撤销与回退"><a href="#撤销与回退" class="headerlink" title="撤销与回退"></a>撤销与回退</h2><p>查看当前仓库状态:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git status</span><br></pre></td></tr></table></figure><p>查看文件更改：</p><figure class="highlight plaintext"><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">$ git difftool &lt;filename&gt;</span><br><span class="line">$ git mergetool &lt;filename&gt;</span><br></pre></td></tr></table></figure><p>查看提交历史:</p><figure class="highlight plaintext"><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">$ git log</span><br><span class="line">$ git log --pretty=oneline #只保留commit id 和 commit message</span><br></pre></td></tr></table></figure><p>撤销工作区<code>Tracked files</code>的修改：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git checkout -- &lt;filename&gt;</span><br></pre></td></tr></table></figure><p>撤销工作区<code>Untracked files</code>的修改：</p><figure class="highlight plaintext"><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><br><span class="line"># n:查看将会删除的文件，防止误删</span><br><span class="line"># f:Untracked的文件</span><br><span class="line"># d:Untracked的目录</span><br><span class="line"># x:包含gitignore的Untracked文件和目录一并删掉，慎用！</span><br><span class="line">#####</span><br><span class="line"></span><br><span class="line">git clean -nfd</span><br><span class="line">git clean -fd</span><br></pre></td></tr></table></figure><p>回退版本区(<code>git commit</code>)和暂存区(<code>git add</code>)，不删除工作空间代码：</p><figure class="highlight plaintext"><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">$ git reset --mixed HEAD^ # --mixed为默认参数</span><br><span class="line">$ git reset HEAD^</span><br></pre></td></tr></table></figure><p>回退版本区(<code>git commit</code>)，暂存区(<code>git add</code>)不回退，不删除工作空间代码：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git reset --soft HEAD^</span><br></pre></td></tr></table></figure><p>回退版本区(<code>git commit</code>)和暂存区(<code>git add</code>)，并删除工作空间代码(不包括<code>Untracked files</code>)，执行后直接恢复到指定<code>&lt;commit-id&gt;</code>状态：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git reset --hard &lt;commit-id&gt;</span><br></pre></td></tr></table></figure><blockquote><p><code>HEAD</code>表示当前版本，<code>HEAD^</code>表示上个版本，<code>HEAD^^</code>表示上上个版本，上100个版本可以表示为<code>HEAD~100</code>以此类推。</p></blockquote><p>回退版本后，若需要返回原来的版本，会发现找不到未来的<code>commit id</code>，则需要查看操作命令历史进行查找：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git reflog</span><br></pre></td></tr></table></figure><p>从版本库删除文件：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git rm &lt;filename&gt;</span><br></pre></td></tr></table></figure><p>若你的代码已经<code>push</code>到线上，则推荐使用下面这个命令回滚：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git revert &lt;commit-id&gt;</span><br></pre></td></tr></table></figure><blockquote><p><code>revert</code>是用一次新的<code>commit</code>来回滚之前的<code>commit</code>，更安全;<code>reset</code>则是直接删除指定的<code>commit</code>，若直接<code>push</code>会导致冲突。</p></blockquote><h2 id="分支"><a href="#分支" class="headerlink" title="分支"></a>分支</h2><p>查看所有分支，有<code>*</code>标记的是当前分支：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git branch -a</span><br></pre></td></tr></table></figure><p>创建本地分支：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git branch &lt;newbranch&gt;</span><br></pre></td></tr></table></figure><p>创建并切换本地分支：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git checkout -b &lt;newbranch&gt;</span><br></pre></td></tr></table></figure><p>从标签创建分支：</p><figure class="highlight plaintext"><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">$ git branch &lt;branch&gt; &lt;tagname&gt;</span><br><span class="line">$ git checkout &lt;branch&gt; # 切换到新建分支</span><br></pre></td></tr></table></figure><p>推送新建本地分支到远程：</p><figure class="highlight plaintext"><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">$ git push -u origin &lt;remote-branch-name&gt;</span><br><span class="line">  or</span><br><span class="line">$ git push --set-upstream origin &lt;remote-branch-name&gt;</span><br></pre></td></tr></table></figure><p>删除本地分支：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git branch -d &lt;branch&gt;</span><br></pre></td></tr></table></figure><blockquote><p>若当前分支因为有修改未提交或其它情况不能删除，请使用<code>-D</code>选项强制删除。</p></blockquote><p>删除远程分支(三种方法)：</p><figure class="highlight plaintext"><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">$ git push origin --delete &lt;remote-branch-name&gt;</span><br><span class="line">$ git push origin -d &lt;remote-branch-name&gt;</span><br><span class="line">$ git push origin :&lt;remote-branch-name&gt;</span><br></pre></td></tr></table></figure><p>清除无用的分支：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git remote prune origin</span><br></pre></td></tr></table></figure><blockquote><p>说明：remote上的一个分支被其他人删除后，需要更新本地的分支列表。</p></blockquote><p>获取远程分支到本地已有分支：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git branch --set-upstream &lt;local-branch&gt; origin/branch</span><br></pre></td></tr></table></figure><p>获取远程分支到本地并新建本地分支：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git checkout -b &lt;local-branch&gt; &lt;remote-branch&gt;</span><br></pre></td></tr></table></figure><p>同步当前分支的更新，使用<code>git pull</code>并不保险：</p><figure class="highlight plaintext"><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"># 下载最新的代码到远程跟踪分支, 即origin/&lt;branch-name&gt;</span><br><span class="line">$ git fetch origin &lt;branch-name&gt; </span><br><span class="line"># 查看更新内容</span><br><span class="line">$ git difftool &lt;branch-name&gt; origin/&lt;branch-name&gt;</span><br><span class="line"># 尝试合并远程跟踪分支的代码到本地分支 </span><br><span class="line">$ git merge origin/&lt;branch-name&gt;</span><br><span class="line"># 借助mergetool解决冲突              </span><br><span class="line">$ git mergetool                               </span><br></pre></td></tr></table></figure><p>同步其它分支的更新，本例拉取<code>master</code>分支更新：</p><figure class="highlight plaintext"><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">$ git fetch origin master</span><br><span class="line">$ git difftool &lt;branch-name&gt; origin/master</span><br><span class="line">$ git merge origin/master</span><br><span class="line">$ git mergetool</span><br></pre></td></tr></table></figure><p>查看某个<code>&lt;commit id&gt;</code>属于哪个分支:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git branch -a --contains &lt;commit id&gt;</span><br></pre></td></tr></table></figure><h2 id="暂存"><a href="#暂存" class="headerlink" title="暂存"></a>暂存</h2><p>当你需要切换分支时,若当前工作区还有些修改没有完成,又不适合提交的,操作切换分支是会提示出错的.这时就需要将这些修改暂存起来:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git stash save &quot;message&quot;</span><br></pre></td></tr></table></figure><p>查看:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git stash list</span><br></pre></td></tr></table></figure><p>恢复:</p><figure class="highlight plaintext"><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">$ git stash pop [--index] [stash@&#123;num&#125;]　</span><br><span class="line">  or</span><br><span class="line">$ git stash apply [--index] [stash@&#123;num&#125;]　# 不删除已恢复的进度.</span><br></pre></td></tr></table></figure><blockquote><p><code>--index</code>表示不仅恢复工作区,还会恢复暂存区;<code>num</code>是你要恢复的操作的序列号,默认恢复最新进度.</p></blockquote><p>删除进度:</p><figure class="highlight plaintext"><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">$ git stash drop [stash@&#123;num&#125;] # 删除指定进度</span><br><span class="line">$ git stash clear # 删除所有</span><br></pre></td></tr></table></figure><h2 id="清理仓库"><a href="#清理仓库" class="headerlink" title="清理仓库"></a>清理仓库</h2><h3 id="清理无用的分支和标签"><a href="#清理无用的分支和标签" class="headerlink" title="清理无用的分支和标签"></a>清理无用的分支和标签</h3><figure class="highlight plaintext"><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">$ git branch -d &lt;branch-name&gt;</span><br><span class="line">$ git tag -d &lt;tag-name&gt;</span><br><span class="line">$ git remote prune origin</span><br><span class="line">$ git pull</span><br></pre></td></tr></table></figure><h3 id="清理大文件"><a href="#清理大文件" class="headerlink" title="清理大文件"></a>清理大文件</h3><ul><li><p>查看<code>git</code>相关文件占用空间：</p><figure class="highlight plaintext"><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">$ git count-objects -v</span><br><span class="line">$ du -sh .git</span><br></pre></td></tr></table></figure></li><li><p>寻找大文件<code>ID</code></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git verify-pack -v .git/objects/pack/*.idx | sort -k 3 -n | tail -10</span><br></pre></td></tr></table></figure><blockquote><p>输出的第一列是文件<code>I</code>D，第二列表示文件<code>（blob）</code>或目录<code>（tree）</code>，第三列是文件大小，此处筛选了最大的10条</p></blockquote></li><li><p>获取文件名与<code>ID</code>映射</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git rev-list --objects --all | grep &quot;$(git verify-pack -v .git/objects/pack/*.idx | sort -k 3 -n | tail -10 | awk &#x27;&#123;print$1&#125;&#x27;)&quot;</span><br></pre></td></tr></table></figure></li><li><p>从所有提交中删除文件</p><figure class="highlight plaintext"><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">$ git filter-branch --tree-filter &#x27;rm -rf xxx&#x27; HEAD --all</span><br><span class="line">$ git pull</span><br></pre></td></tr></table></figure></li><li><p>清理<code>.git</code>目录:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git gc --prune=now</span><br></pre></td></tr></table></figure><blockquote><p>tips: 在执行<code>push</code>操作时，<code>git</code>会自动执行一次<code>gc</code>操作，不过只有<code>loose object</code>达到一定数量后才会真正调用，建议手动执行。</p></blockquote></li></ul><h3 id="处理大型二进制文件"><a href="#处理大型二进制文件" class="headerlink" title="处理大型二进制文件"></a>处理大型二进制文件</h3><p>由于git在存储二进制文件时效率不高,所以需要借助<a href="http://www.oschina.net/news/71365/git-annex-lfs-bigfiles-fat-media-bigstore-sym">第三方组件</a>。</p><h2 id="忽略特殊文件"><a href="#忽略特殊文件" class="headerlink" title="忽略特殊文件"></a>忽略特殊文件</h2><p>当你的仓库中有一些文件，类似密码或者数据库文件不需要提交但又必须放在仓库目录下，每次<code>git status</code>都会提示<code>Untracked</code>，看着让人很不爽，提供两种方法解决这个问题</p><h3 id="本地"><a href="#本地" class="headerlink" title="本地"></a>本地</h3><p>在代码仓库目录创建一个<code>.gitignore</code>文件，编写规则如下：</p><figure class="highlight plaintext"><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">tmp/  # 忽略tmp文件夹下所有内容</span><br><span class="line">*.ini # 忽略所有ini文件</span><br><span class="line">!data/ #忽略除了data文件夹的所有内容</span><br></pre></td></tr></table></figure><h3 id="全局"><a href="#全局" class="headerlink" title="全局"></a>全局</h3><p>在用户目录创建一个<code>.gitignore_global</code>文件，编写规则同<code>.gitignore</code>，并修改<code>~/.gitconfig</code></p><figure class="highlight plaintext"><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">[core]</span><br><span class="line">excludesfile = ~/.gitignore_global</span><br></pre></td></tr></table></figure><p>如果添加的忽略对象已经<code>Tracked</code>，纳入了版本管理中，则需要在代码仓库中先把本地缓存删除,改变成<code>Untracked</code>状态</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git rm -r --cached .</span><br></pre></td></tr></table></figure><blockquote><p><a href="https://github.com/github/gitignore"><code>.gitignore</code>模版</a></p></blockquote><h2 id="奇技淫巧"><a href="#奇技淫巧" class="headerlink" title="奇技淫巧"></a>奇技淫巧</h2><h3 id="重写历史（慎用！）"><a href="#重写历史（慎用！）" class="headerlink" title="重写历史（慎用！）"></a>重写历史（慎用！）</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git rebase -i [git-hash| head~n]</span><br></pre></td></tr></table></figure><blockquote><p>其中<code>git-hash</code>是你要开始进行<code>rebase</code>的<code>commit</code>的<code>hash</code>，而<code>head~n</code>则是从<code>HEAD</code>向前推<code>n</code>个<code>commit</code></p></blockquote><h3 id="全局更换电子邮件"><a href="#全局更换电子邮件" class="headerlink" title="全局更换电子邮件"></a>全局更换电子邮件</h3><figure class="highlight plaintext"><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">git filter-branch --commit-filter &#x27;</span><br><span class="line">        if [ &quot;$GIT_AUTHOR_EMAIL&quot; = &quot;xxx@localhost&quot; ];</span><br><span class="line">        then</span><br><span class="line">                GIT_AUTHOR_NAME=&quot;xxx&quot;;</span><br><span class="line">                GIT_AUTHOR_EMAIL=&quot;xxx@example.com&quot;;</span><br><span class="line">                git commit-tree &quot;$@&quot;;</span><br><span class="line">        else</span><br><span class="line">                git commit-tree &quot;$@&quot;;</span><br><span class="line">        fi&#x27; HEAD --all</span><br></pre></td></tr></table></figure><h2 id="帮助"><a href="#帮助" class="headerlink" title="帮助"></a>帮助</h2><p>查看帮助：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git --help</span><br></pre></td></tr></table></figure><h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><ol><li><a href="http://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000">廖雪峰老师的git教程</a></li><li><a href="http://www.ruanyifeng.com/blog/2015/12/git-cheat-sheet.html">常用Git命令清单</a></li><li><a href="https://git-scm.com/book/en/v2">Git-Book</a></li><li><a href="https://git-scm.com/docs">Git-Reference</a></li><li><a href="https://segmentfault.com/a/1190000002783245">Git push与pull的默认行为</a></li><li><a href="http://www.tuicool.com/articles/rUBNBvI">git stash 详解</a></li></ol>]]></content>
    
    
    <summary type="html">&lt;p&gt;本文是对之前&lt;a href=&quot;http://answerywj.com/2016/08/28/Git%E9%80%9F%E6%9F%A5%E6%89%8B%E5%86%8C/&quot;&gt;Git速查手册&lt;/a&gt;的更新，增加了一些这段时间使用到的命令。&lt;/p&gt;</summary>
    
    
    
    <category term="Git" scheme="https://answerywj.com/categories/Git/"/>
    
    
    <category term="git" scheme="https://answerywj.com/tags/git/"/>
    
  </entry>
  
  <entry>
    <title>C代码覆盖率测试工具Gcov</title>
    <link href="https://answerywj.com/2018/09/25/coverage-of-code/"/>
    <id>https://answerywj.com/2018/09/25/coverage-of-code/</id>
    <published>2018-09-25T10:48:49.000Z</published>
    <updated>2023-03-20T06:26:35.314Z</updated>
    
    <content type="html"><![CDATA[<p>代码覆盖率测试反映了测试的广度与深度，量化了测试和开发质量，是十分有必要的，业界目前有针对各种语言的覆盖率测试工具，本文主要介绍<code>C/C++</code>相关的覆盖率测试工具<code>Gcov</code></p><span id="more"></span><h2 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h2><h3 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h3><p><code>Gcov</code>是一个测试覆盖程序，是集成在<code>GCC</code>中的，随<code>GCC</code>一起发布</p><h3 id="基本概念"><a href="#基本概念" class="headerlink" title="基本概念"></a>基本概念</h3><h4 id="基本块BB"><a href="#基本块BB" class="headerlink" title="基本块BB"></a>基本块BB</h4><p>基本块指一段程序的第一条语句被执行过一次后，这段程序中的每一跳语句都需要执行一次，称为基本块，因此基本块中的所有语句的执行次数是相同的，一般由多个顺序执行语句后边跟一个跳转语句组成</p><h4 id="跳转ARC"><a href="#跳转ARC" class="headerlink" title="跳转ARC"></a>跳转ARC</h4><p>从一个<code>BB</code>到另外一个<code>BB</code>的跳转叫做一个<code>ARC</code>,要想知道程序中的每个语句和分支的执行次数，就必须知道每个<code>BB</code>和<code>ARC</code>的执行次数</p><h4 id="程序流图"><a href="#程序流图" class="headerlink" title="程序流图"></a>程序流图</h4><p>如果把<code>BB</code>作为一个节点，这样一个函数中的所有<code>BB</code>就构成了一个有向图，要想知道程序中的每个语句和分支的执行次数，就必须知道每个<code>BB</code>和<code>ARC</code>的执行次数，根据图论可以知道有向图中<code>BB</code>的入度和出度是相同的，所以只要知道了部分的<code>BB</code>或者<code>ARC</code>大小，就可以推断所有的大小，这里选择由<code>ARC</code>的执行次数来推断<code>BB</code>的执行次数，所以对部分<code>ARC</code>插桩，只要满足可以统计出来所有的<code>BB</code>和<code>ARC</code>的执行次数即可</p><h3 id="原理"><a href="#原理" class="headerlink" title="原理"></a>原理</h3><p>测试程序首先进行编译预处理，生成汇编文件，并完成插桩，插桩的过程中会向源文件的末尾插入一个静态数组，数组的大小就是这个源文件中桩点的个数，数组的值就是桩点的执行次数，每个桩点插入3~4条汇编语句，直接插入生成的<code>*.s</code>文件中，最后汇编文件经过汇编生成目标文件，在程序运行过程中桩点负责收集程序的执行信息</p><h2 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h2><h3 id="编译"><a href="#编译" class="headerlink" title="编译"></a>编译</h3><p>测试代码如下：<br><code>say.c</code>:</p><figure class="highlight plaintext"><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">#include &lt;stdio.h&gt;</span><br><span class="line"></span><br><span class="line">int say(char *what) &#123;</span><br><span class="line">    printf(&quot;------ %s\n&quot;, what);</span><br><span class="line">    return 0;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>main.c</code></p><figure class="highlight plaintext"><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">#include &lt;stdio.h&gt;</span><br><span class="line"></span><br><span class="line">extern int say(const char *);</span><br><span class="line"></span><br><span class="line">int main(int argc, const char *argv[]) &#123;</span><br><span class="line">    </span><br><span class="line">    if (argv[1]) &#123;</span><br><span class="line">        say(&quot;hello&quot;);</span><br><span class="line">    &#125; else &#123;</span><br><span class="line">        say(&quot;bye&quot;);</span><br><span class="line">    &#125;</span><br><span class="line">    return 0;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>添加<code>-fprofile-arcs -ftest-coverage -fPIC</code>编译参数编译程序，生成可执行程序和<code>*.gcno</code>文件，里面记录了行信息和程序流图信息：</p><figure class="highlight plaintext"><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 -fprofile-arcs -ftest-coverage -fPIC -O0 say.c main.c</span><br><span class="line"></span><br><span class="line">$ ls</span><br><span class="line">a.out  main.c  main.gcno  say.c  say.gcno  </span><br></pre></td></tr></table></figure><h3 id="数据收集"><a href="#数据收集" class="headerlink" title="数据收集"></a>数据收集</h3><p>运行可执行文件，生成<code>*.gcda</code>在默认生成在相应<code>*.o</code>文件目录，里面记录了<code>*.c</code>文件中程序的执行情况，包括跳变次数等:</p><figure class="highlight plaintext"><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">$ ./a.out</span><br><span class="line">------ bye</span><br><span class="line"></span><br><span class="line">$ ls</span><br><span class="line">a.out  main.c  main.gcda  main.gcno  say.c  say.gcda  say.gcno</span><br></pre></td></tr></table></figure><p>可以通过设置环境变量<code>GCOV_PREFIX=/xxx/xxx</code>和<code>GCOV_PREFIX_STRIP=x</code>来改变路径，其中<code>GCOV_PREFIX_STRIP</code>表示去掉源代码路径中的前几级，默认为<code>0</code>，比如源代码路径为<code>/a/b/c/d.c</code>，<code>GCOV_PREFIX_STRIP=2</code>，则实际使用的路径是<code>c/d.c</code>，如果<code>GCOV_PREFIX=/e/f</code>，则<code>.gcda</code>实际存放的路径是<code>/e/f/c/d.gcda</code></p><h3 id="报告生成"><a href="#报告生成" class="headerlink" title="报告生成"></a>报告生成</h3><p>针对某一个文件的执行情况，可以通过如下命令生成报告，并创建<code>*.gcov</code>文件：</p><figure class="highlight plaintext"><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">$ gcov -a main.c</span><br><span class="line">File &#x27;main.c&#x27;</span><br><span class="line">Lines executed:80.00% of 5</span><br><span class="line">Creating &#x27;main.c.gcov&#x27;</span><br></pre></td></tr></table></figure><p>常用选项，更多可参考<a href="https://gcc.gnu.org/onlinedocs/gcc/Invoking-Gcov.html#Invoking-Gcov">Invoking gcov</a>：</p><figure class="highlight plaintext"><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">-b：分支覆盖</span><br><span class="line">-a：所有基本块覆盖</span><br><span class="line">-f：函数覆盖</span><br></pre></td></tr></table></figure><h3 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h3><ol><li>在编译时不要加优化选项，否则代码会发生变化，无法准确定位</li><li>代码中复杂的宏，比如宏展开后是循环或者其他控制结构，可以用内联函数来代替，因为<code>gcov</code>只统计宏调用出现的那一行</li><li>代码每一行最好只有一条语句</li><li><code>*.gcno</code>与<code>*.gcda</code>需要匹配，两个文件是有时间戳来记录是不是匹配的</li><li>若是编译动态库，需要在链接时<code>-lgcov</code></li></ol><h3 id="图形化展示"><a href="#图形化展示" class="headerlink" title="图形化展示"></a>图形化展示</h3><p><code>gcov</code>生成的报告分散在各个源码文件所对应的<code>*.gcov</code>文件中，难以汇总分析，并且可视化效果较差，所以需要转化成可视图形化报告，有<code>lcov</code>或<code>gcovr</code>两个工具可以完成，两者功能基本相同，本文主要介绍<code>gcovr</code>，是一个用<code>Python</code>编写的开源软件，大小只有几十KB，安装参见<a href="https://gcovr.com/installation.html">官网</a></p><h4 id="列表形式"><a href="#列表形式" class="headerlink" title="列表形式"></a>列表形式</h4><ol><li><p>代码覆盖率</p><figure class="highlight plaintext"><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">$ gcovr -r .</span><br><span class="line">------------------------------------------------------------------------------</span><br><span class="line">                           GCC Code Coverage Report</span><br><span class="line">Directory: .</span><br><span class="line">------------------------------------------------------------------------------</span><br><span class="line">File                                       Lines    Exec  Cover   Missing</span><br><span class="line">------------------------------------------------------------------------------</span><br><span class="line">main.c                                         5       4    80%   15</span><br><span class="line">say.c                                          3       3   100%   </span><br><span class="line">------------------------------------------------------------------------------</span><br><span class="line">TOTAL                                          8       7    87%</span><br><span class="line">------------------------------------------------------------------------------</span><br></pre></td></tr></table></figure><p>报告展示程序运行后覆盖了<code>80%</code>的代码</p></li><li><p>分支覆盖率</p><figure class="highlight plaintext"><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">$ gcovr -b -r .</span><br><span class="line">------------------------------------------------------------------------------</span><br><span class="line">                           GCC Code Coverage Report</span><br><span class="line">Directory: .</span><br><span class="line">------------------------------------------------------------------------------</span><br><span class="line">File                                    Branches   Taken  Cover   Missing</span><br><span class="line">------------------------------------------------------------------------------</span><br><span class="line">main.c                                         2       1    50%   14</span><br><span class="line">say.c                                          0       0    --%   </span><br><span class="line">------------------------------------------------------------------------------</span><br><span class="line">TOTAL                                          2       1    50%</span><br><span class="line">------------------------------------------------------------------------------</span><br></pre></td></tr></table></figure><p>报告展示了在<code>main.c</code>中有一个分支没有执行到</p></li></ol><h4 id="XML文件形式"><a href="#XML文件形式" class="headerlink" title="XML文件形式"></a>XML文件形式</h4><figure class="highlight plaintext"><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></pre></td><td class="code"><pre><span class="line">$ gcovr --xml-pretty -r .</span><br><span class="line">&lt;?xml version=&quot;1.0&quot; ?&gt;</span><br><span class="line">&lt;!DOCTYPE coverage</span><br><span class="line">  SYSTEM &#x27;http://cobertura.sourceforge.net/xml/coverage-04.dtd&#x27;&gt;</span><br><span class="line">&lt;coverage branch-rate=&quot;0.5&quot; branches-covered=&quot;1&quot; branches-valid=&quot;2&quot;</span><br><span class="line"> complexity=&quot;0.0&quot; line-rate=&quot;0.875&quot; lines-covered=&quot;7&quot; lines-valid=&quot;8&quot;</span><br><span class="line"> timestamp=&quot;1537930892&quot; version=&quot;gcovr 3.4&quot;&gt;</span><br><span class="line"> &lt;sources&gt;</span><br><span class="line">  &lt;source&gt;.&lt;/source&gt;</span><br><span class="line"> &lt;/sources&gt;</span><br><span class="line"> &lt;packages&gt;</span><br><span class="line">  &lt;package branch-rate=&quot;0.5&quot; complexity=&quot;0.0&quot; line-rate=&quot;0.875&quot; name=&quot;&quot;&gt;</span><br><span class="line">   &lt;classes&gt;</span><br><span class="line">    &lt;class branch-rate=&quot;0.5&quot; complexity=&quot;0.0&quot; filename=&quot;main.c&quot;</span><br><span class="line">     line-rate=&quot;0.8&quot; name=&quot;main_c&quot;&gt;</span><br><span class="line">     &lt;methods/&gt;</span><br><span class="line">     &lt;lines&gt;</span><br><span class="line">      &lt;line branch=&quot;false&quot; hits=&quot;1&quot; number=&quot;12&quot;/&gt;</span><br><span class="line">      &lt;line branch=&quot;true&quot; condition-coverage=&quot;50% (1/2)&quot; hits=&quot;1&quot; number=&quot;14&quot;&gt;</span><br><span class="line">       &lt;conditions&gt;</span><br><span class="line">        &lt;condition coverage=&quot;50%&quot; number=&quot;0&quot; type=&quot;jump&quot;/&gt;</span><br><span class="line">       &lt;/conditions&gt;</span><br><span class="line">      &lt;/line&gt;</span><br><span class="line">      &lt;line branch=&quot;false&quot; hits=&quot;0&quot; number=&quot;15&quot;/&gt;</span><br><span class="line">      &lt;line branch=&quot;false&quot; hits=&quot;1&quot; number=&quot;17&quot;/&gt;</span><br><span class="line">      &lt;line branch=&quot;false&quot; hits=&quot;1&quot; number=&quot;19&quot;/&gt;</span><br><span class="line">     &lt;/lines&gt;</span><br><span class="line">    &lt;/class&gt;</span><br><span class="line">    &lt;class branch-rate=&quot;0.0&quot; complexity=&quot;0.0&quot; filename=&quot;say.c&quot; line-rate=&quot;1.0&quot;</span><br><span class="line">     name=&quot;say_c&quot;&gt;</span><br><span class="line">     &lt;methods/&gt;</span><br><span class="line">     &lt;lines&gt;</span><br><span class="line">      &lt;line branch=&quot;false&quot; hits=&quot;1&quot; number=&quot;10&quot;/&gt;</span><br><span class="line">      &lt;line branch=&quot;false&quot; hits=&quot;1&quot; number=&quot;11&quot;/&gt;</span><br><span class="line">      &lt;line branch=&quot;false&quot; hits=&quot;1&quot; number=&quot;12&quot;/&gt;</span><br><span class="line">     &lt;/lines&gt;</span><br><span class="line">    &lt;/class&gt;</span><br><span class="line">   &lt;/classes&gt;</span><br><span class="line">  &lt;/package&gt;</span><br><span class="line"> &lt;/packages&gt;</span><br><span class="line">&lt;/coverage&gt;</span><br></pre></td></tr></table></figure><h4 id="HTML文件形式"><a href="#HTML文件形式" class="headerlink" title="HTML文件形式"></a>HTML文件形式</h4><figure class="highlight plaintext"><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">$ gcovr -r . --html -o xxx.html</span><br><span class="line">$ ls</span><br><span class="line">a.out  main.c  main.gcda  main.gcno  say.c  say.gcda  say.gcno  xxx.html</span><br></pre></td></tr></table></figure><p>可以发现添加<code>--html</code>参数后，可以生成<code>html</code>文件，用浏览器打开，如下图：<br><img src="/2018/09/25/coverage-of-code/gcovr_xxx.png" alt="gcovr_xxx.png"></p><p>还可以添加<code>--html-details</code>选项，为每个代码文件单独生成<code>html</code></p><figure class="highlight plaintext"><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">$ gcovr -r . --html --html-details -o xxx.html</span><br><span class="line">$ ls</span><br><span class="line">a.out  main.c  main.gcda  main.gcno  say.c  say.gcda  say.gcno  xxx.html  xxx.main.c.html  xxx.say.c.html</span><br></pre></td></tr></table></figure><p>可以发现多了<code>xxx.main.c.html</code>和<code>xxx.say.c.html</code>，用浏览器打开<code>xxx.html</code>，如下图：<br><img src="/2018/09/25/coverage-of-code/gcovr_xxx_detail.png" alt="gcovr_xxx_detail.png"><br>文件名较之前带上了下划线，单击文件名，可以看到具体的代码覆盖情况，如下图：<br><img src="/2018/09/25/coverage-of-code/gcovr_xxx_main.png" alt="gcovr_xxx_main.png"></p><h4 id="其它"><a href="#其它" class="headerlink" title="其它"></a>其它</h4><p>其它功能，如<code>Filters</code>等，可以参考<a href="https://gcovr.com/guide.html">官方文档</a></p><h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><ul><li><a href="https://gcc.gnu.org/onlinedocs/gcc/Gcov.html#Gcov">gcov—a Test Coverage Program</a></li><li><a href="https://blog.csdn.net/bryanlai0720/article/details/38729535">关于C++ code coverage tool 的研究 —GCOV 实现原理</a></li><li><a href="https://gcovr.com/installation.html">gcovr官网</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;p&gt;代码覆盖率测试反映了测试的广度与深度，量化了测试和开发质量，是十分有必要的，业界目前有针对各种语言的覆盖率测试工具，本文主要介绍&lt;code&gt;C/C++&lt;/code&gt;相关的覆盖率测试工具&lt;code&gt;Gcov&lt;/code&gt;&lt;/p&gt;</summary>
    
    
    
    <category term="C/C++" scheme="https://answerywj.com/categories/C-C/"/>
    
    
    <category term="代码覆盖率" scheme="https://answerywj.com/tags/%E4%BB%A3%E7%A0%81%E8%A6%86%E7%9B%96%E7%8E%87/"/>
    
  </entry>
  
  <entry>
    <title>cJSON的秘密</title>
    <link href="https://answerywj.com/2018/05/03/cjson/"/>
    <id>https://answerywj.com/2018/05/03/cjson/</id>
    <published>2018-05-03T08:58:02.000Z</published>
    <updated>2023-03-20T06:26:35.314Z</updated>
    
    <content type="html"><![CDATA[<p>学习使用cJSON过程的一些发现和总结，不涉及具体的函数</p><span id="more"></span><h2 id="cJSON简介"><a href="#cJSON简介" class="headerlink" title="cJSON简介"></a>cJSON简介</h2><p><code>cJSON</code>是一个快速，高性能的<code>json</code>解析器，由<code>C</code>语言编写，仅包含<code>cJSON.c</code>和<code>cJSON.h</code>两个文件，不支持跨平台；跨平台推荐纯<code>lua</code>写的<a href="http://dkolf.de/src/dkjson-lua.fsl/home"><code>dkjson</code></a></p><h2 id="cJSON结构体"><a href="#cJSON结构体" class="headerlink" title="cJSON结构体"></a>cJSON结构体</h2><p><code>cJSON</code>结构体的组成：</p><figure class="highlight plaintext"><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">typedef struct cJSON &#123;</span><br><span class="line">struct cJSON *next, *prev;</span><br><span class="line">struct cJSON *child;</span><br><span class="line"></span><br><span class="line">int type;</span><br><span class="line"></span><br><span class="line">char *valuestring;</span><br><span class="line">int valueint;</span><br><span class="line">double valuedouble;</span><br><span class="line"></span><br><span class="line">char *string;</span><br><span class="line">&#125; cJSON;</span><br></pre></td></tr></table></figure><p>其中</p><ul><li><code>next</code>指向链表中下一个兄弟节点，<code>prev</code>指向本节点前一个节点</li><li><code>child</code>节点只有对象和数组有，并且<code>child</code>节点是双向链表的头节点，<code>child</code>的<code>prev</code>一般为<code>NULL</code>，不指向任何节点，双向链表的最后一个兄弟节点的<code>next</code>是无指向的</li><li><code>type</code>取值有<code>Null/True/False/Number/String/Array/Object</code>，这些值类型都在<code>cJSON.h</code>中通过宏定义了</li><li><code>String</code>类型节点有<code>valuestring </code>，<code>Number</code>类型节点有<code>valueint</code>和<code>valuedouble</code></li><li><code>string</code>表示节点的名称，所有的节点都是一个链表，都具有<code>string</code>值</li></ul><blockquote><p><code>cJSON</code>默认所有值都为<code>0</code>，除非额外为其赋有意义的值</p></blockquote><h3 id="cJSON树结构"><a href="#cJSON树结构" class="headerlink" title="cJSON树结构"></a>cJSON树结构</h3><p><code>cJSON</code>使用树结构存储<code>JSON</code>的各个节点，而这个树结构是使用双向链表实现的(实线表示节点间有真实的引用关系，而虚线表示逻辑上的引用关系)：<br><img src="/2018/05/03/cjson/cjson-tree.png" alt="cJSON树结构"></p><ul><li>树结构的每一层都是一个双向链表，表示一堆兄弟节点</li><li>当前层的所有节点都是当前链表头节点的父节点的子节点</li></ul><p>下面举例说明：</p><figure class="highlight plaintext"><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">&#123;</span><br><span class="line">    &quot;name&quot;: &quot;Jack (\&quot;Bee\&quot;) Nimble&quot;, </span><br><span class="line">    &quot;format&quot;: &#123;</span><br><span class="line">        &quot;type&quot;:       &quot;rect&quot;, </span><br><span class="line">        &quot;width&quot;:      1920, </span><br><span class="line">        &quot;height&quot;:     1080, </span><br><span class="line">        &quot;interlace&quot;:  false, </span><br><span class="line">        &quot;frame rate&quot;: 24</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><code>name</code>和<code>format</code>节点组成一个链表，<code>type</code>、<code>width</code>、<code>height</code>、<code>interlace</code>和<code>frame rate</code>节点组成一个链表</li><li>根节点包含节点类型<code>Object</code>和子节点<code>name</code></li><li>子节点包含节点名称<code>name</code>、节点值<code>Jack (&quot;Bee&quot;) Nimble</code>和兄弟节点<code>format</code></li><li><code>format</code>节点包含节点类型<code>Object</code>、节点名称<code>format</code>和子节点<code>type</code></li><li><code>type</code>节点包含节点类型<code>String</code>、节点名称<code>type</code>、节点值<code>rect</code>和兄弟节点<code>width</code></li><li><code>width</code>节点包含节点类型<code>Number</code>、节点名称<code>width</code>、节点值<code>1920</code>和兄弟节点<code>height</code></li><li><code>height</code>节点包含节点类型<code>Number</code>、节点名称<code>height</code>、节点值<code>1080</code>和兄弟节点<code>interlace</code> </li><li><code>interlace</code>节点包含节点类型<code>False</code>、节点名称<code>interlace</code>和兄弟节点<code>frame rate</code></li><li><code>frame rate</code>节点包含节点类型<code>Number</code>、节点名称<code>frame tate</code>和节点值<code>25</code></li></ul><h2 id="cJSON内存管理"><a href="#cJSON内存管理" class="headerlink" title="cJSON内存管理"></a>cJSON内存管理</h2><p><code>cJson</code>分为自动和手动两种使用方式：</p><ul><li>在自动模式下，<code>cJSON</code>使用默认的<code>malloc</code>和<code>free</code>函数管理内存，在<code>cJSON</code>中，每个节点都是<code>malloc</code>而来，每个节点的<code>string</code>和<code>valuestring</code>也是<code>malloc</code>而来，使用<code>cJSON_Delete</code>函数可以递归释放<code>JSON</code>树中<code>malloc</code>的节点内存和字符内存，使用<code>cJSON_Print</code>函数后，则需要手动释放<code>cJSON_Print</code>函数分配的内存，避免内存泄露</li><li>在手动模式下，<code>cJSON</code>提供了钩子函数来帮助用户自定义内存管理函数，如果不设置，这默认为<code>malloc</code>和<code>free</code></li></ul><figure class="highlight plaintext"><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">struct cJSON_Hooks js_hook = &#123;xxx_malloc, xxx_free&#125;;</span><br><span class="line">cJSON_InitHooks(&amp;js_hook);</span><br></pre></td></tr></table></figure><h2 id="cJSON序列化"><a href="#cJSON序列化" class="headerlink" title="cJSON序列化"></a>cJSON序列化</h2><p><code>cJSON</code>序列化就是把<code>cJSON</code>输出，有两种形式：</p><ul><li>格式化输出<code>char  *cJSON_Print(cJSON *item);</code></li><li>压缩输出<code>char  *cJSON_PrintUnformatted(cJSON *item);</code></li></ul><p>需要注意的是<code>cJSON</code>采用了预先将要输的内容全部以字符串形式存储在内存中，最后输出整个字符串的方法，而不是边分析<code>json</code>数据边输出，所以对于比较大的<code>json</code>数据来说，内存就是个问题了</p><h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><ul><li><a href="https://github.com/faycheng/cJSON">cJson源码和源码分析</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;p&gt;学习使用cJSON过程的一些发现和总结，不涉及具体的函数&lt;/p&gt;</summary>
    
    
    
    <category term="C/C++" scheme="https://answerywj.com/categories/C-C/"/>
    
    
    <category term="cJSON" scheme="https://answerywj.com/tags/cJSON/"/>
    
  </entry>
  
  <entry>
    <title>Linux下core文件使用</title>
    <link href="https://answerywj.com/2018/03/07/usage-of-core-in-linux/"/>
    <id>https://answerywj.com/2018/03/07/usage-of-core-in-linux/</id>
    <published>2018-03-07T08:16:15.000Z</published>
    <updated>2023-03-20T06:26:35.342Z</updated>
    
    <content type="html"><![CDATA[<p>有时候程序会异常退出而不带任何日志，此时就可以使用<code>core</code>文件进行分析，它会记录程序运行的内存，寄存器，堆栈指针等信息</p><span id="more"></span><h2 id="什么是core文件"><a href="#什么是core文件" class="headerlink" title="什么是core文件"></a>什么是core文件</h2><p>通常在<code>Linux</code>下遇到程序异常退出或者中止，我们都会使用<code>core</code>文件进行分析，其中包含了程序运行时的内存，寄存器，堆栈指针等信息，格式为<code>ELF</code>，可以理解是程序工作当前状态转储成一个文件，通过工具分析这个文件，我们可以定位到程序异常退出或者终止时相应的堆栈调用等信息，为解决问题提供帮助。</p><h2 id="使用core文件调试"><a href="#使用core文件调试" class="headerlink" title="使用core文件调试"></a>使用core文件调试</h2><h3 id="生成方法"><a href="#生成方法" class="headerlink" title="生成方法"></a>生成方法</h3><ol><li><p>查看当前<code>core</code>文件的状态</p><figure class="highlight plaintext"><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">$ ulimit -a</span><br><span class="line">...</span><br><span class="line">-c: core file size (blocks)         0  # 关闭状态</span><br><span class="line">...</span><br></pre></td></tr></table></figure></li><li><p>打开生成开关</p><figure class="highlight plaintext"><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">ulimit -c unlimited</span><br><span class="line">ulimit -a</span><br><span class="line">...</span><br><span class="line">-c: core file size (blocks)         unlimited</span><br><span class="line">...</span><br></pre></td></tr></table></figure></li><li><p>对<code>core</code>文件的大小进行限制，单位为<code>blocks</code>，一般<code>1 block=512 bytes</code>，设置太小可能导致不会生成文件</p><figure class="highlight plaintext"><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">$ ulimit -c 1024</span><br><span class="line">$ ulimit -a</span><br><span class="line">...</span><br><span class="line">-c: core file size (blocks)         1024</span><br><span class="line">...</span><br></pre></td></tr></table></figure></li><li><p>关闭生成开关</p><figure class="highlight plaintext"><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">ulimit -c 0</span><br><span class="line">ulimit -a</span><br><span class="line">...</span><br><span class="line">-c: core file size (blocks)         0</span><br><span class="line">...</span><br></pre></td></tr></table></figure></li></ol><blockquote><p>上面对<code>core</code>文件的操作仅对当前生效，若需要永久生效，则要将相应操作写入<code>/etc/profile</code></p></blockquote><h3 id="生成路径"><a href="#生成路径" class="headerlink" title="生成路径"></a>生成路径</h3><p><code>core</code>文件默认生成在程序的工作目录，可以对生成路径进行设置，需要保证对对应目录有足够空间并具有写权限</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">echo /MyCoreDumpDir/core.%e.%p &gt; /proc/sys/kernel/core_pattern</span><br></pre></td></tr></table></figure><p>其中命名使用的参数列表</p><figure class="highlight plaintext"><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">%p - insert pid into filename  # 添加 pid </span><br><span class="line">%u - insert current uid into filename  # 添加当前 uid </span><br><span class="line">%g - insert current gid into filename  # 添加当前 gid </span><br><span class="line">%s - insert signal that caused the coredump into the filename  # 添加导致产生 core 的信号 </span><br><span class="line">%t - insert UNIX time that the coredump occurred into filename  # 添加 core 文件生成时的 unix 时间 </span><br><span class="line">%h - insert hostname where the coredump happened into filename  # 添加主机名 </span><br><span class="line">%e - insert coredumping executable name into filename  # 添加命令名</span><br></pre></td></tr></table></figure><blockquote><p><code>/proc/sys/kernel/core_uses_pid</code>这个文件的值若为１，则无论是否配置<code>%p</code>,最后生成的<code>core</code>文件都会添加<code>pid</code></p></blockquote><h3 id="调试方法"><a href="#调试方法" class="headerlink" title="调试方法"></a>调试方法</h3><p>可以使用<code>gdb</code>对<code>core</code>文件进行调试，编译时需要带上<code>-g</code>选项</p><figure class="highlight plaintext"><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">$ gdb a.out</span><br><span class="line">...</span><br><span class="line">(gdb) core-file core</span><br><span class="line">...</span><br><span class="line">(gdb) bt </span><br><span class="line">...</span><br></pre></td></tr></table></figure><p>如需要在<code>PC</code>上调试嵌入式设备产生的<code>core</code>文件，则需要选取相应平台的<code>gdb</code>工具，并在进入<code>gdb</code>后设置符号文件的位置</p><figure class="highlight plaintext"><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">$ xxx-xxx-gdb a.out</span><br><span class="line">...</span><br><span class="line">(gdb) solib-search-path xxx.so:xxx.so</span><br><span class="line">...</span><br><span class="line">(gdb) core-file core</span><br><span class="line">...</span><br><span class="line">(gdb) bt</span><br><span class="line">...</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">&lt;p&gt;有时候程序会异常退出而不带任何日志，此时就可以使用&lt;code&gt;core&lt;/code&gt;文件进行分析，它会记录程序运行的内存，寄存器，堆栈指针等信息&lt;/p&gt;</summary>
    
    
    
    <category term="C/C++" scheme="https://answerywj.com/categories/C-C/"/>
    
    
    <category term="core dump" scheme="https://answerywj.com/tags/core-dump/"/>
    
  </entry>
  
  <entry>
    <title>Speex介绍</title>
    <link href="https://answerywj.com/2017/12/29/speex/"/>
    <id>https://answerywj.com/2017/12/29/speex/</id>
    <published>2017-12-29T07:49:34.000Z</published>
    <updated>2023-03-20T06:26:35.338Z</updated>
    
    <content type="html"><![CDATA[<p>本文将对<em>Speex</em>相关的概念进行介绍</p><span id="more"></span><p>[TOC]</p><p><em>Speex</em>编解码器是一款开源且免费的语音编解码器，遵循<em>BSD</em>协议，为分封网络（<em>packet network</em>）和网络电话（<em>VoIP</em>）而设计，支持文件的压缩</p><blockquote><p>为网络电话而不是移动电话而设计，意味着<em>Speex</em>对数据丢失具有鲁棒性，但是对数据包损坏不鲁棒，在<em>VoIP</em>中的数据包要么完整到达，要么不能到达</p></blockquote><p><em>Speex</em>选用CELP（码激励线性预测编码）编码技术，在高比特率和低比特率都稳定可靠，复杂性适度并且占用内存较少</p><h2 id="相关概念"><a href="#相关概念" class="headerlink" title="相关概念"></a>相关概念</h2><h3 id="采样率"><a href="#采样率" class="headerlink" title="采样率"></a>采样率</h3><p>采样率是每秒钟采集到的信号样本数，单位是<em>Hertz</em>（<em>Hz</em>），<em>Speex</em>为三种不同的采样率而设计：<em>8kHz</em>（窄带），<em>16kHz</em>（宽带）和<em>32kHz</em>（超宽带）</p><h3 id="比特率"><a href="#比特率" class="headerlink" title="比特率"></a>比特率</h3><p>在对语音信号编码时，比特率定义为单位时间内的比特数，单位是比特每秒（<em>bps</em>）或通常的千比特每秒（<em>kbps</em>）</p><blockquote><p>注意千比特每秒（<em>kbps</em>）和千字节每秒（<em>kBps</em>）的区别。</p></blockquote><h3 id="质量（可变）"><a href="#质量（可变）" class="headerlink" title="质量（可变）"></a>质量（可变）</h3><p><em>Speex</em>是有损的编解码器，意味着压缩率以输入语音信号的保真度为代价，质量越高，压缩后的比特率越大，音质也越好<br><em>Speex</em>可以控制质量和比特率之间的折中，大多数时间由一个范围在<em>0</em>到<em>10</em>之间的质量参数控制</p><blockquote><p>在不变比特率（<em>CBR</em>）中，质量参数是一个整数； 在可变比特率（<em>VBR</em>）中，质量参数是一个浮点数。</p></blockquote><h3 id="复杂度（可变）"><a href="#复杂度（可变）" class="headerlink" title="复杂度（可变）"></a>复杂度（可变）</h3><p><em>Speex</em>允许编码器拥有可变的复杂度，通过一个范围在<em>1</em>到<em>10</em>之间的整数控制搜索的执行来实现，复杂度越高，压缩率越高，CPU使用率越高，音质越好，类似于<em>gzip</em>和<em>bzip2</em>压缩工具的-1到*-9<em>选项<br>正常使用情况下，复杂度为</em>1<em>的噪声等级比复杂度为</em>10<em>的噪声等级高</em>1<em>到</em>2<em>个</em>dB<em>，但复杂度为</em>10<em>的</em>CPU<em>要求比复杂度为1的高</em>5*倍。</p><blockquote><p>实际应用中，最好的折中是复杂度<em>2</em>到<em>4</em>，但在编码非语音声音如<em>DTMF</em>声调时更高的复杂度经常被用到</p></blockquote><h3 id="可变比特率（VBR）"><a href="#可变比特率（VBR）" class="headerlink" title="可变比特率（VBR）"></a>可变比特率（<em>VBR</em>）</h3><p>可变比特率（<em>VBR</em>）允许编解码器自适应的根据待编码音频的“难度”动态地改变比特率，如元音和高能瞬态变化的声音需要高比特率以获得好的质量； 但是摩擦音（如<em>s，f</em>）用低比特率就能充分编码</p><ul><li>优点：<em>VBR</em>在相同的质量下能获得更低的比特率，或在不变比特率下获得更好的质量</li><li>缺点：在指定质量情况下，无法保证最终的平均比特率；在一些如网络电话（<em>VoIP</em>）这样的实时应用中，依赖于最大比特率，这在通信信道中必须足够低。</li></ul><h3 id="平均比特率（ABR）"><a href="#平均比特率（ABR）" class="headerlink" title="平均比特率（ABR）"></a>平均比特率（<em>ABR</em>）</h3><p>平均比特率解决了<em>VBR</em>中的一个问题，它动态地调整<em>VBR</em>质量以获得指定的比特率，因为质量和比特率是实时调整的，<em>ABR</em>的全局质量比正好达到目标平均比特率的<em>VBR</em>编码质量稍微差些。</p><h3 id="声音活动检测（VAD）"><a href="#声音活动检测（VAD）" class="headerlink" title="声音活动检测（VAD）"></a>声音活动检测（<em>VAD</em>）</h3><p><em>VAD</em>检测待编码的音频是语音还是无声&#x2F;背景噪声，<em>VBR</em>编码中默认激活</p><blockquote><p><em>Speex</em>检测出非语言段并仅使用足够复现背景噪声的比特率进行编码，这叫“柔化噪音生成”（<em>CNG</em>）。</p></blockquote><h3 id="断续传输（DTX）"><a href="#断续传输（DTX）" class="headerlink" title="断续传输（DTX）"></a>断续传输（DTX）</h3><p>断续传输是<em>VAD&#x2F;VBR</em>的附加操作，当背景噪声平稳时会完全停止传输</p><h3 id="知觉增强"><a href="#知觉增强" class="headerlink" title="知觉增强"></a>知觉增强</h3><p>知觉增强是解码器的一部分，当被启用时，能减少编解码过程中产生的噪声或失真的知觉</p><blockquote><p>在大多数情况下，知觉增强会带来声音客观上的偏离（如仅考虑<em>SNR</em>），但最后仍听起来更好（主管增强）</p></blockquote><h3 id="等待时间和算法延时"><a href="#等待时间和算法延时" class="headerlink" title="等待时间和算法延时"></a>等待时间和算法延时</h3><p>每一个语音编解码器在传输中都会引入延时，对于<em>Speex</em>，延时等于帧长加上处理每一帧需要前几帧的数量</p><blockquote><p>在窄带操作中延时为<em>30ms</em>，在宽带操作中延时为<em>34ms</em>，这不包括编解码帧时的<em>CPU</em>时间</p></blockquote><h2 id="相关组件"><a href="#相关组件" class="headerlink" title="相关组件"></a>相关组件</h2><h3 id="编解码器"><a href="#编解码器" class="headerlink" title="编解码器"></a>编解码器</h3><p><em>Speex</em>编解码器有以下特性</p><ol><li>免费软件&#x2F;开源，免专利费和版税</li><li>利用嵌入比特流集成了窄带和宽带</li><li>大范围可用比特率（从<em>2.15kbps</em>到<em>44kbps</em>）</li><li>动态比特率转换（<em>AMR</em>）和可变比特率操作（<em>VBR</em>）</li><li>声音活动检测（<em>VAD</em>，与<em>VBR</em>集成）和断续传输（<em>DTX</em>）</li><li>可变复杂度</li><li>嵌入宽带结构（可伸缩采样率）</li><li><em>32kHz</em>超宽带采样率</li><li>强度立体声编码选项</li><li>定点实现</li></ol><h3 id="预处理器"><a href="#预处理器" class="headerlink" title="预处理器"></a>预处理器</h3><p>预处理器在对音频编码前对音频进行预处理，有三个主要功能</p><ol><li>噪声抑制<br>先降噪再进行编解码是有好处的，因为<em>Speex</em>编解码器通常会对噪声输入同样进行编解码，这将会扩大噪声，而降噪能大大减少这一影响</li><li>自动增益控制（<em>AGC</em>）<br>自动增益控制（<em>AGC</em>）是为了处理录音音量在不同设置里有很大差别这一问题，<em>AGC</em>将会调整信号音量到参考音量大小</li><li>声音活动检测（<em>VAD</em>）<br>预处理器提供的声音活动检测（<em>VAD</em>）比编解码器中直接提供的<em>VAD</em>更先进</li></ol><h3 id="自适应抖动缓冲器"><a href="#自适应抖动缓冲器" class="headerlink" title="自适应抖动缓冲器"></a>自适应抖动缓冲器</h3><p>当通过<em>UDP</em>（<em>User Datagram Protocal</em>，用户数据报协议）或<em>RTP</em>（<em>Real Time Protocal</em>，实时传输协议）传输声音（或其他任何内容）时，数据包可能丢失，不同延时到达，甚至乱序，抖动缓冲器的作用是对数据包进行重排序并保存在足够长的buffer（但有一定限度）里，然后将数据包发送去解码</p><h3 id="声学回声消除器"><a href="#声学回声消除器" class="headerlink" title="声学回声消除器"></a>声学回声消除器</h3><p>在任何免提式通信系统中（下图），远端的语音在本地扬声器播放时，经过在房间里传播后又会被麦克风录音，如果将麦克风录音直接又发送到远端，则远端的用户将会听到他自己的回声<br><img src="/2017/12/29/speex/aec.png" alt="aec"><br>声学回声消除器就是为了在将录音发送到远端前消除声学回声，提高了远端接收的语音质量</p><h3 id="重采样器"><a href="#重采样器" class="headerlink" title="重采样器"></a>重采样器</h3><p>重采样指转换音频的采样率，在任意采样率间进行转换（采样率必须是有理数），能控制质量和复杂度的折中，可用于能混合不同采样率流，支持声卡不支持的采样率，能转码等</p><h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><ul><li><a href="http://blog.csdn.net/YJJat1989/article/category/1879965">http://blog.csdn.net/YJJat1989/article/category/1879965</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;p&gt;本文将对&lt;em&gt;Speex&lt;/em&gt;相关的概念进行介绍&lt;/p&gt;</summary>
    
    
    
    <category term="语音" scheme="https://answerywj.com/categories/%E8%AF%AD%E9%9F%B3/"/>
    
    
    <category term="Speex" scheme="https://answerywj.com/tags/Speex/"/>
    
  </entry>
  
  <entry>
    <title>线程同步机制条件变量的使用与思考</title>
    <link href="https://answerywj.com/2017/12/15/condition-variables-of-thread-synchronization/"/>
    <id>https://answerywj.com/2017/12/15/condition-variables-of-thread-synchronization/</id>
    <published>2017-12-15T14:23:08.000Z</published>
    <updated>2023-03-20T06:26:35.314Z</updated>
    
    <content type="html"><![CDATA[<p>条件变量是<em>Linux</em>线程同步的一种机制，与互斥量一起使用时，允许线程以无竞争的方式等待特定条件的发生</p><span id="more"></span><p>[TOC]</p><h2 id="关键函数"><a href="#关键函数" class="headerlink" title="关键函数"></a>关键函数</h2><h3 id="初始化与注销"><a href="#初始化与注销" class="headerlink" title="初始化与注销"></a>初始化与注销</h3><figure class="highlight plaintext"><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">#include &lt;pthread.h&gt;</span><br><span class="line"></span><br><span class="line">// 静态初始化</span><br><span class="line">pthread_cond_t cond = PTHREAD_COND_INITIALIZER;</span><br><span class="line"></span><br><span class="line">// 动态初始化</span><br><span class="line">int pthread_cond_init(thread_cond_t *cond, </span><br><span class="line">                      const pthread_condattr_t *attr);</span><br><span class="line"></span><br><span class="line">// 反初始化，即注销</span><br><span class="line">int pthread_cond_destroy(pthread_cond_t *cond);</span><br><span class="line"></span><br><span class="line">返回值: 若成功，返回0；否则，返回错误编码</span><br></pre></td></tr></table></figure><p>注意：</p><ul><li>只有在没有线程在该条件变量上等待时，才可以注销条件变量，否则会返回<code>EBUSY</code></li><li><code>Linux</code>在实现条件变量时，并没有为条件变量分配资源，所以在注销一个条件变量时，只需要注意该变量是否仍有等待线程即可</li></ul><h3 id="线程等待"><a href="#线程等待" class="headerlink" title="线程等待"></a>线程等待</h3><figure class="highlight plaintext"><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">#include &lt;pthread.h&gt;</span><br><span class="line"></span><br><span class="line">int pthread_cond_wait(pthread_cond_t *cond, </span><br><span class="line">                      pthread_mutex_t *mutex);</span><br><span class="line"></span><br><span class="line">int pthread_cond_timedwait(pthread_cond_t *cond, </span><br><span class="line">                           pthread_mutex_t *mutex, </span><br><span class="line">                           const struct timespec *abstime);</span><br><span class="line"></span><br><span class="line">返回值: 若成功，返回0；否则，返回错误编码</span><br></pre></td></tr></table></figure><p>执行过程如下：</p><ol><li>调用者把锁住的互斥量传给函数，然后函数自动把调用线程放到等待条件的线程列表上</li><li>对互斥量进行解锁，线程挂起进入等待(不占用<code>CPU</code>时间)　</li><li>函数被唤醒返回时，会自动对互斥量进行加锁</li></ol><blockquote><p><code>pthread_cond_timedwait</code>只是多了一个等待超时时间，通过<code>timespec</code>指定，超时返回错误<code>ETIMEDOUT</code></p></blockquote><h3 id="线程唤醒"><a href="#线程唤醒" class="headerlink" title="线程唤醒"></a>线程唤醒</h3><figure class="highlight plaintext"><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">#include &lt;pthread.h&gt;</span><br><span class="line"></span><br><span class="line">int pthread_cond_signal(pthread_cond_t *cond);</span><br><span class="line"></span><br><span class="line">int pthread_cond_broadcast(pthread_cond_t *cond);</span><br><span class="line"></span><br><span class="line">返回值: 若成功，返回0；否则，返回错误编码</span><br></pre></td></tr></table></figure><ul><li><code>pthread_cond_signal</code>至少能唤醒一个等待该条件的线程</li><li><code>pthread_cond_broadcast</code>则能唤醒等待该条件的所有线程<blockquote><p>需要注意的是，一定要在改变条件状态以后再给线程发信号</p></blockquote></li></ul><h2 id="示例"><a href="#示例" class="headerlink" title="示例"></a>示例</h2><p>示例代码可参考我的<a href="https://github.com/AnSwErYWJ/DogFood/blob/master/C/thread/t_cond.c">github</a>，由于篇幅原因，不在此贴出</p><h2 id="一些思考"><a href="#一些思考" class="headerlink" title="一些思考"></a>一些思考</h2><h3 id="条件变量实质是什么"><a href="#条件变量实质是什么" class="headerlink" title="条件变量实质是什么"></a>条件变量实质是什么</h3><p>条件变量实质是利用线程间共享的全局变量进行同步的一种机制</p><h3 id="互斥量保护的是什么"><a href="#互斥量保护的是什么" class="headerlink" title="互斥量保护的是什么"></a>互斥量保护的是什么</h3><p>示例中的相关代码</p><figure class="highlight plaintext"><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">pthread_mutex_lock(&amp;(test-&gt;mut));</span><br><span class="line"></span><br><span class="line">while (test-&gt;condition == 0)</span><br><span class="line">&#123;</span><br><span class="line">  pthread_cond_wait(&amp;(test-&gt;cond), &amp;(test-&gt;mut));</span><br><span class="line">&#125;</span><br><span class="line">    </span><br><span class="line">pthread_mutex_unlock(&amp;(test-&gt;mut));</span><br></pre></td></tr></table></figure><p>互斥量是用来保护条件<code>test-&gt;condition</code>在读取时，它的值不被其它线程修改，如果条件成立，则此线程进入等待条件的线程队列，对互斥量进行解锁并开始等待</p><h3 id="为什么用while来判断条件"><a href="#为什么用while来判断条件" class="headerlink" title="为什么用while来判断条件"></a>为什么用while来判断条件</h3><p>如上面的代码所示，使用<code>while</code>对条件进行判断的原因如下：</p><ol><li>若先解锁互斥量，再唤醒等待线程，则条件可能被其它线程更改，使得等待条件再次成立，需要继续等待</li><li><code>pthread_cond_wait</code>可能存在意外返回的情况，则此时条件并没有被更改，需要继续等待。<blockquote><p>造成意外返回的原因是<code>Linux</code>中带阻塞功能的系统调用都会在进程收到<code>signal</code>后返回</p></blockquote></li></ol><h3 id="先唤醒线程还是先解锁"><a href="#先唤醒线程还是先解锁" class="headerlink" title="先唤醒线程还是先解锁"></a>先唤醒线程还是先解锁</h3><p>示例代码：</p><ol><li><p>情况一：先唤醒</p><figure class="highlight plaintext"><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">pthread_mutex_lock(&amp;(test-&gt;mut));</span><br><span class="line">test-&gt;condition = 1</span><br><span class="line">pthread_cond_signal(&amp;(test-&gt;cond));</span><br><span class="line">pthread_mutex_unlock(&amp;(test-&gt;mut));</span><br></pre></td></tr></table></figure></li><li><p>情况二：先解锁</p><figure class="highlight plaintext"><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">pthread_mutex_lock(&amp;(test-&gt;mut));</span><br><span class="line">test-&gt;condition = 1</span><br><span class="line">pthread_mutex_unlock(&amp;(test-&gt;mut));</span><br><span class="line">pthread_cond_signal(&amp;(test-&gt;cond));</span><br></pre></td></tr></table></figure><p>两种情况各有缺点：</p></li></ol><ul><li>情况一在唤醒等待线程后，再解锁，使得等待线程在被唤醒后试图对互斥量进行加锁时，互斥量还未解锁，则线程又进入睡眠，待互斥量解锁成功后，再次被唤醒并对互斥量加锁，这样就会发生两次上下文切换，影响性能</li><li>情况二在唤醒等待线程前先解锁，使得其它线程可能先于等待线程获取互斥量，并对条件进行更改，使得条件变量失去作用</li></ul><h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><ul><li><a href="https://www.cnblogs.com/leijiangtao/p/4028338.html">关于pthread_cond_wait使用while循环判断的理解</a></li><li><a href="https://www.cnblogs.com/zhx831/p/3543633.html">Linux线程同步之条件变量pthread_cond_t</a></li><li><a href="">APUE</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;p&gt;条件变量是&lt;em&gt;Linux&lt;/em&gt;线程同步的一种机制，与互斥量一起使用时，允许线程以无竞争的方式等待特定条件的发生&lt;/p&gt;</summary>
    
    
    
    <category term="C/C++" scheme="https://answerywj.com/categories/C-C/"/>
    
    
    <category term="pthread" scheme="https://answerywj.com/tags/pthread/"/>
    
  </entry>
  
</feed>
