|
|
<!DOCTYPE html>
|
|
|
<html lang="zh-cn" dir="ltr">
|
|
|
<head>
|
|
|
<meta charset="UTF-8">
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
<meta name="description" content="编译流程 # Go语言是一门编译型语言,程序运行时需要先编译成相应平台的可执行文件。在介绍Go语言编译流程之前,我们来了解下编译器编译整个流程。
|
|
|
编译六阶段 # 编译器工作目的是完成从高级语言(high-level langue)到机器码(machine code)的输出。整个编译流程可分为两大部分,每个部分可以细分为3个阶段。两大部分别是分析部分(Analysis part)以及合成部分(Synthesis part),这两大部分也称为编译前端和编译后端。编译六阶段如下:
|
|
|
词法分析(Lexical analysis) 语法分析(Syntax analysis) 语义分析(Semantic analysis) 中间码生成(Intermediate code generator) 代码优化(Code optimizer) 机器代码生成(Code generator) 词法分析 # 词法分析最终生成的是Tokens。词法分析时编译器扫描源代码,从当前行最左端开始到最右端,然后将扫描到的字符进行分组标记。编译器会将扫描到的词法单位(Lexemes)归类到常量、保留字、运算符等标记(Tokens)中。我们以c = a+b*5为例:
|
|
|
Lexemes Tokens c identifier = assignment symbol a identifier + + (addition symbol) b identifier * * (multiplication symbol) 5 5 (number) 语法分析 # 词法分析阶段接收上一阶段生成的Tokens序列,基于特定编程语言的规则生成抽象语法树(Abstract Syntax Tree)。
|
|
|
抽象语法树 # 抽象语法树(Abstract Syntax Tree),简称AST,是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。
|
|
|
以(a+b)*c为例,最终生成的抽象语法树如下:
|
|
|
语义分析阶段 # 语义分析阶段用来检查代码的语义一致性。它使用前一阶段的语法树以及符号表来验证给定的源代码在语义上是一致的。它还检查代码是否传达了适当的含义。
|
|
|
中间码生成阶段 # 中间代码介是于高级语言和机器语言之间,具有跨平台特性。使用中间代码可以易于跨平台转换为特定类型目标机器代码。
|
|
|
代码优化阶段 # 代码优化阶段主要是改进中间代码,删除不必要的代码,以调整代码序列以生成速度更快和空间更少的中间代码。">
|
|
|
<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">
|
|
|
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#343a40">
|
|
|
<meta name="color-scheme" content="light dark"><meta property="og:title" content="Go编译流程" />
|
|
|
<meta property="og:description" content="编译流程 # Go语言是一门编译型语言,程序运行时需要先编译成相应平台的可执行文件。在介绍Go语言编译流程之前,我们来了解下编译器编译整个流程。
|
|
|
编译六阶段 # 编译器工作目的是完成从高级语言(high-level langue)到机器码(machine code)的输出。整个编译流程可分为两大部分,每个部分可以细分为3个阶段。两大部分别是分析部分(Analysis part)以及合成部分(Synthesis part),这两大部分也称为编译前端和编译后端。编译六阶段如下:
|
|
|
词法分析(Lexical analysis) 语法分析(Syntax analysis) 语义分析(Semantic analysis) 中间码生成(Intermediate code generator) 代码优化(Code optimizer) 机器代码生成(Code generator) 词法分析 # 词法分析最终生成的是Tokens。词法分析时编译器扫描源代码,从当前行最左端开始到最右端,然后将扫描到的字符进行分组标记。编译器会将扫描到的词法单位(Lexemes)归类到常量、保留字、运算符等标记(Tokens)中。我们以c = a+b*5为例:
|
|
|
Lexemes Tokens c identifier = assignment symbol a identifier + + (addition symbol) b identifier * * (multiplication symbol) 5 5 (number) 语法分析 # 词法分析阶段接收上一阶段生成的Tokens序列,基于特定编程语言的规则生成抽象语法树(Abstract Syntax Tree)。
|
|
|
抽象语法树 # 抽象语法树(Abstract Syntax Tree),简称AST,是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。
|
|
|
以(a+b)*c为例,最终生成的抽象语法树如下:
|
|
|
语义分析阶段 # 语义分析阶段用来检查代码的语义一致性。它使用前一阶段的语法树以及符号表来验证给定的源代码在语义上是一致的。它还检查代码是否传达了适当的含义。
|
|
|
中间码生成阶段 # 中间代码介是于高级语言和机器语言之间,具有跨平台特性。使用中间代码可以易于跨平台转换为特定类型目标机器代码。
|
|
|
代码优化阶段 # 代码优化阶段主要是改进中间代码,删除不必要的代码,以调整代码序列以生成速度更快和空间更少的中间代码。" />
|
|
|
<meta property="og:type" content="article" />
|
|
|
<meta property="og:url" content="https://go.cyub.vip/compiler/" /><meta property="article:section" content="" />
|
|
|
|
|
|
|
|
|
<title>Go编译流程 | 深入Go语言之旅</title>
|
|
|
<link rel="manifest" href="/manifest.json">
|
|
|
<link rel="icon" href="/favicon.png" >
|
|
|
<link rel="stylesheet" href="/book.min.f06572240ce28e67eb332ac5cf5d59a696c47ad4c6f700d5842c5ed93dd8ec77.css" integrity="sha256-8GVyJAzijmfrMyrFz11ZppbEetTG9wDVhCxe2T3Y7Hc=" crossorigin="anonymous">
|
|
|
<script defer src="/flexsearch.min.js"></script>
|
|
|
<script defer src="/en.search.min.7e9d53d4a20eea8c87bf76a4502bd21aa041c1ef2adc7e37ffc5339c57accccd.js" integrity="sha256-fp1T1KIO6oyHv3akUCvSGqBBwe8q3H43/8UznFeszM0=" crossorigin="anonymous"></script>
|
|
|
<!--
|
|
|
Made with Book Theme
|
|
|
https://github.com/alex-shpak/hugo-book
|
|
|
-->
|
|
|
|
|
|
</head>
|
|
|
<body dir="ltr">
|
|
|
<input type="checkbox" class="hidden toggle" id="menu-control" />
|
|
|
<input type="checkbox" class="hidden toggle" id="toc-control" />
|
|
|
<main class="container flex">
|
|
|
<aside class="book-menu">
|
|
|
<div class="book-menu-content">
|
|
|
|
|
|
<nav>
|
|
|
<h2 class="book-brand">
|
|
|
<a class="flex align-center" href="/"><img src="http://go2.cyub.vip/_static/golang-480.png" alt="Logo" /><span>深入Go语言之旅</span>
|
|
|
</a>
|
|
|
</h2>
|
|
|
|
|
|
|
|
|
<div class="book-search">
|
|
|
<input type="text" id="book-search-input" placeholder="Search" aria-label="Search" maxlength="64" data-hotkeys="s/" />
|
|
|
<div class="book-search-spinner hidden"></div>
|
|
|
<ul id="book-search-results"></ul>
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<ul>
|
|
|
|
|
|
<li>
|
|
|
<a href="https://www.cyub.vip/" target="_blank" rel="noopener">
|
|
|
个人博客
|
|
|
</a>
|
|
|
</li>
|
|
|
|
|
|
<li>
|
|
|
<a href="https://github.com/cyub" target="_blank" rel="noopener">
|
|
|
Github主页
|
|
|
</a>
|
|
|
</li>
|
|
|
|
|
|
<li>
|
|
|
<a href="https://www.topgoer.cn/?ref=go.cyub.vip" target="_blank" rel="noopener">
|
|
|
地鼠文档
|
|
|
</a>
|
|
|
</li>
|
|
|
|
|
|
</ul>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<ul>
|
|
|
<li>
|
|
|
<p><strong>准备篇</strong></p>
|
|
|
<ul>
|
|
|
<li>
|
|
|
<a href="/compiler/"class=active>编译流程</a></li>
|
|
|
<li>
|
|
|
<a href="/analysis-tools/">分析工具</a>
|
|
|
<ul>
|
|
|
<li>
|
|
|
<a href="/analysis-tools/gdb/">GDB</a></li>
|
|
|
<li>
|
|
|
<a href="/analysis-tools/dlv/">Delve</a></li>
|
|
|
<li>
|
|
|
<a href="/analysis-tools/go-buildin-tools/">Go 内置工具</a></li>
|
|
|
</ul>
|
|
|
</li>
|
|
|
<li>
|
|
|
<a href="/go-assembly/">Go汇编</a></li>
|
|
|
</ul>
|
|
|
</li>
|
|
|
<li>
|
|
|
<p><strong>基础篇</strong></p>
|
|
|
<ul>
|
|
|
<li>
|
|
|
<a href="/type/">数据类型与数据结构</a>
|
|
|
<ul>
|
|
|
<li>
|
|
|
<a href="/type/string/">字符串</a></li>
|
|
|
<li>
|
|
|
<a href="/type/array/">数组</a></li>
|
|
|
<li>
|
|
|
<a href="/type/slice/">切片</a></li>
|
|
|
<li>
|
|
|
<a href="/type/nil/">nil</a></li>
|
|
|
<li>
|
|
|
<a href="/type/empty_struct/">空结构体</a></li>
|
|
|
<li>
|
|
|
<a href="/type/pointer/">指针</a></li>
|
|
|
<li>
|
|
|
<a href="/type/map/">映射</a></li>
|
|
|
</ul>
|
|
|
</li>
|
|
|
<li>
|
|
|
<a href="/function/">函数</a>
|
|
|
<ul>
|
|
|
<li>
|
|
|
<a href="/function/first-class/">一等公民</a></li>
|
|
|
<li>
|
|
|
<a href="/function/call-stack/">函数调用栈</a></li>
|
|
|
<li>
|
|
|
<a href="/function/pass-by-value/">值传递</a></li>
|
|
|
<li>
|
|
|
<a href="/function/closure/">闭包</a></li>
|
|
|
<li>
|
|
|
<a href="/function/method/">方法</a></li>
|
|
|
</ul>
|
|
|
</li>
|
|
|
<li>
|
|
|
<a href="/feature/">语言特性</a>
|
|
|
<ul>
|
|
|
<li>
|
|
|
<a href="/feature/comma-ok/">逗号ok模式</a></li>
|
|
|
<li>
|
|
|
<a href="/feature/for-range/">遍历 - for-range语法</a></li>
|
|
|
<li>
|
|
|
<a href="/feature/defer/">延迟执行 - defer语法</a></li>
|
|
|
<li>
|
|
|
<a href="/feature/select/">通道选择器 - select语法</a></li>
|
|
|
<li>
|
|
|
<a href="/feature/panic-recover/">恐慌与恢复 - panic/recover</a></li>
|
|
|
</ul>
|
|
|
</li>
|
|
|
</ul>
|
|
|
</li>
|
|
|
<li>
|
|
|
<p><strong>运行时篇</strong></p>
|
|
|
<ul>
|
|
|
<li>
|
|
|
<a href="/concurrency/">并发编程</a>
|
|
|
<ul>
|
|
|
<li>
|
|
|
<a href="/concurrency/memory-model/">内存模型</a></li>
|
|
|
<li>
|
|
|
<a href="/concurrency/context/">上下文 - context</a></li>
|
|
|
<li>
|
|
|
<a href="/concurrency/channel/">通道 - channel</a></li>
|
|
|
<li>
|
|
|
<a href="/concurrency/atomic/">原子操作 - atomic</a></li>
|
|
|
<li>
|
|
|
<a href="/concurrency/sync-map/">并发Map - sync.Map</a></li>
|
|
|
<li>
|
|
|
<a href="/concurrency/sync-waitgroup/">等待组 - sync.WaitGroup</a></li>
|
|
|
<li>
|
|
|
<a href="/concurrency/sync-once/">一次性操作 - sync.Once</a></li>
|
|
|
<li>
|
|
|
<a href="/concurrency/sync-pool/">缓冲池 - sync.Pool</a></li>
|
|
|
<li>
|
|
|
<a href="/concurrency/sync-cond/">条件变量 - sync.Cond</a></li>
|
|
|
<li>
|
|
|
<a href="/concurrency/sync-mutex/">互斥锁 - sync.Mutex</a></li>
|
|
|
<li>
|
|
|
<a href="/concurrency/sync-rwmutex/">读写锁 - sync.RWMutex</a></li>
|
|
|
</ul>
|
|
|
</li>
|
|
|
<li>
|
|
|
<a href="/gmp/">G-M-P调度机制</a>
|
|
|
<ul>
|
|
|
<li>
|
|
|
<a href="/gmp/gmp-model/">调度机制概述</a></li>
|
|
|
<li>
|
|
|
<a href="/gmp/scheduler/">调度器</a></li>
|
|
|
</ul>
|
|
|
</li>
|
|
|
<li>
|
|
|
<a href="/memory/">内存管理</a>
|
|
|
<ul>
|
|
|
<li>
|
|
|
<a href="/memory/allocator/">内存分配器</a></li>
|
|
|
<li>
|
|
|
<a href="/memory/gc/">GC</a></li>
|
|
|
</ul>
|
|
|
</li>
|
|
|
<li>
|
|
|
<a href="/type-system/">类型系统</a>
|
|
|
<ul>
|
|
|
<li>
|
|
|
<a href="/type-system/type/">类型系统</a></li>
|
|
|
<li>
|
|
|
<a href="/type-system/interface/">接口</a></li>
|
|
|
<li>
|
|
|
<a href="/type-system/reflect/">反射</a></li>
|
|
|
</ul>
|
|
|
</li>
|
|
|
</ul>
|
|
|
</li>
|
|
|
</ul>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
</nav>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<script>(function(){var e=document.querySelector("aside .book-menu-content");addEventListener("beforeunload",function(){localStorage.setItem("menu.scrollTop",e.scrollTop)}),e.scrollTop=localStorage.getItem("menu.scrollTop")})()</script>
|
|
|
|
|
|
|
|
|
|
|
|
</div>
|
|
|
</aside>
|
|
|
|
|
|
<div class="book-page">
|
|
|
<header class="book-header">
|
|
|
|
|
|
<div class="flex align-center justify-between">
|
|
|
<label for="menu-control">
|
|
|
<img src="/svg/menu.svg" class="book-icon" alt="Menu" />
|
|
|
</label>
|
|
|
|
|
|
<strong>Go编译流程</strong>
|
|
|
|
|
|
<label for="toc-control">
|
|
|
|
|
|
</label>
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
</header>
|
|
|
|
|
|
|
|
|
|
|
|
<article class="markdown"><h1 id="编译流程">
|
|
|
编译流程
|
|
|
<a class="anchor" href="#%e7%bc%96%e8%af%91%e6%b5%81%e7%a8%8b">#</a>
|
|
|
</h1>
|
|
|
<p>Go语言是一门编译型语言,程序运行时需要先编译成相应平台的可执行文件。在介绍Go语言编译流程之前,我们来了解下编译器编译整个流程。</p>
|
|
|
<h2 id="编译六阶段">
|
|
|
编译六阶段
|
|
|
<a class="anchor" href="#%e7%bc%96%e8%af%91%e5%85%ad%e9%98%b6%e6%ae%b5">#</a>
|
|
|
</h2>
|
|
|
<p>编译器工作目的是完成从高级语言(high-level langue)到机器码(machine code)的输出。整个编译流程可分为两大部分,每个部分可以细分为3个阶段。两大部分别是分析部分(Analysis part)以及合成部分(Synthesis part),这两大部分也称为编译前端和编译后端。编译六阶段如下:</p>
|
|
|
<ul>
|
|
|
<li>词法分析(Lexical analysis)</li>
|
|
|
<li>语法分析(Syntax analysis)</li>
|
|
|
<li>语义分析(Semantic analysis)</li>
|
|
|
<li>中间码生成(Intermediate code generator)</li>
|
|
|
<li>代码优化(Code optimizer)</li>
|
|
|
<li>机器代码生成(Code generator)</li>
|
|
|
</ul>
|
|
|
<h3 id="词法分析">
|
|
|
词法分析
|
|
|
<a class="anchor" href="#%e8%af%8d%e6%b3%95%e5%88%86%e6%9e%90">#</a>
|
|
|
</h3>
|
|
|
<p>词法分析最终生成的是Tokens。词法分析时编译器扫描源代码,从当前行最左端开始到最右端,然后将扫描到的字符进行分组标记。编译器会将扫描到的词法单位(Lexemes)归类到常量、保留字、运算符等标记(Tokens)中。我们以<code>c = a+b*5</code>为例:</p>
|
|
|
<table>
|
|
|
<thead>
|
|
|
<tr>
|
|
|
<th>Lexemes</th>
|
|
|
<th>Tokens</th>
|
|
|
</tr>
|
|
|
</thead>
|
|
|
<tbody>
|
|
|
<tr>
|
|
|
<td>c</td>
|
|
|
<td>identifier</td>
|
|
|
</tr>
|
|
|
<tr>
|
|
|
<td>=</td>
|
|
|
<td>assignment symbol</td>
|
|
|
</tr>
|
|
|
<tr>
|
|
|
<td>a</td>
|
|
|
<td>identifier</td>
|
|
|
</tr>
|
|
|
<tr>
|
|
|
<td>+</td>
|
|
|
<td>+ (addition symbol)</td>
|
|
|
</tr>
|
|
|
<tr>
|
|
|
<td>b</td>
|
|
|
<td>identifier</td>
|
|
|
</tr>
|
|
|
<tr>
|
|
|
<td>*</td>
|
|
|
<td>* (multiplication symbol)</td>
|
|
|
</tr>
|
|
|
<tr>
|
|
|
<td>5</td>
|
|
|
<td>5 (number)</td>
|
|
|
</tr>
|
|
|
</tbody>
|
|
|
</table>
|
|
|
<h3 id="语法分析">
|
|
|
语法分析
|
|
|
<a class="anchor" href="#%e8%af%ad%e6%b3%95%e5%88%86%e6%9e%90">#</a>
|
|
|
</h3>
|
|
|
<p>词法分析阶段接收上一阶段生成的Tokens序列,基于特定编程语言的规则生成抽象语法树(Abstract Syntax Tree)。</p>
|
|
|
<h4 id="抽象语法树">
|
|
|
抽象语法树
|
|
|
<a class="anchor" href="#%e6%8a%bd%e8%b1%a1%e8%af%ad%e6%b3%95%e6%a0%91">#</a>
|
|
|
</h4>
|
|
|
<p>抽象语法树(Abstract Syntax Tree),简称AST,是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。</p>
|
|
|
<p>以<code>(a+b)*c</code>为例,最终生成的抽象语法树如下:</p>
|
|
|
<p>
|
|
|
<img src="https://static.cyub.vip/images/202104/ast.png" alt="" /></p>
|
|
|
<h3 id="语义分析阶段">
|
|
|
语义分析阶段
|
|
|
<a class="anchor" href="#%e8%af%ad%e4%b9%89%e5%88%86%e6%9e%90%e9%98%b6%e6%ae%b5">#</a>
|
|
|
</h3>
|
|
|
<p>语义分析阶段用来检查代码的语义一致性。它使用前一阶段的语法树以及符号表来验证给定的源代码在语义上是一致的。它还检查代码是否传达了适当的含义。</p>
|
|
|
<h3 id="中间码生成阶段">
|
|
|
中间码生成阶段
|
|
|
<a class="anchor" href="#%e4%b8%ad%e9%97%b4%e7%a0%81%e7%94%9f%e6%88%90%e9%98%b6%e6%ae%b5">#</a>
|
|
|
</h3>
|
|
|
<p>中间代码介是于高级语言和机器语言之间,具有跨平台特性。使用中间代码可以易于跨平台转换为特定类型目标机器代码。</p>
|
|
|
<h3 id="代码优化阶段">
|
|
|
代码优化阶段
|
|
|
<a class="anchor" href="#%e4%bb%a3%e7%a0%81%e4%bc%98%e5%8c%96%e9%98%b6%e6%ae%b5">#</a>
|
|
|
</h3>
|
|
|
<p>代码优化阶段主要是改进中间代码,删除不必要的代码,以调整代码序列以生成速度更快和空间更少的中间代码。</p>
|
|
|
<h3 id="机器码生成阶段">
|
|
|
机器码生成阶段
|
|
|
<a class="anchor" href="#%e6%9c%ba%e5%99%a8%e7%a0%81%e7%94%9f%e6%88%90%e9%98%b6%e6%ae%b5">#</a>
|
|
|
</h3>
|
|
|
<p>机器码生成阶段是编译器工作的最后阶段。此阶段会基于中间码生成汇编代码,汇编器根据汇编代码生成目标文件,目标文件经过链接器处理最终生成可执行文件。</p>
|
|
|
<h2 id="go语言编译流程">
|
|
|
Go语言编译流程
|
|
|
<a class="anchor" href="#go%e8%af%ad%e8%a8%80%e7%bc%96%e8%af%91%e6%b5%81%e7%a8%8b">#</a>
|
|
|
</h2>
|
|
|
<p>上面介绍了编译器工作整个流程,Go语言编译器编译也符合上面流程:</p>
|
|
|
<pre tabindex="0"><code class="language-eval_rst" data-lang="eval_rst">.. image:: https://static.cyub.vip/images/202104/go-compile.png
|
|
|
:alt: Go语言编译流程
|
|
|
:width: 500px
|
|
|
:align: center
|
|
|
</code></pre><p>我们执行<code>go build</code>命令时候,带上<code>-n</code>选项可以观察编译流程所执行所有的命令:</p>
|
|
|
<pre tabindex="0"><code class="language-eval_rst" data-lang="eval_rst">.. code-block::
|
|
|
:emphasize-lines: 11,24
|
|
|
|
|
|
#
|
|
|
# command-line-arguments
|
|
|
#
|
|
|
|
|
|
mkdir -p $WORK/b001/
|
|
|
cat >$WORK/b001/importcfg << 'EOF' # internal
|
|
|
# import config
|
|
|
packagefile runtime=/usr/lib/go/pkg/linux_amd64/runtime.a
|
|
|
EOF
|
|
|
cd /home/vagrant/dive-into-go
|
|
|
/usr/lib/go/pkg/tool/linux_amd64/compile -o $WORK/b001/_pkg_.a -trimpath "$WORK/b001=>" -p main -complete -buildid aJhlsTb17ElgWQeF76b5/aJhlsTb17ElgWQeF76b5 -goversion go1.14.13 -D _/home/vagrant/dive-into-go -importcfg $WORK/b001/importcfg -pack ./empty_string.go
|
|
|
/usr/lib/go/pkg/tool/linux_amd64/buildid -w $WORK/b001/_pkg_.a # internal
|
|
|
cat >$WORK/b001/importcfg.link << 'EOF' # internal
|
|
|
packagefile command-line-arguments=$WORK/b001/_pkg_.a
|
|
|
packagefile runtime=/usr/lib/go/pkg/linux_amd64/runtime.a
|
|
|
packagefile internal/bytealg=/usr/lib/go/pkg/linux_amd64/internal/bytealg.a
|
|
|
packagefile internal/cpu=/usr/lib/go/pkg/linux_amd64/internal/cpu.a
|
|
|
packagefile runtime/internal/atomic=/usr/lib/go/pkg/linux_amd64/runtime/internal/atomic.a
|
|
|
packagefile runtime/internal/math=/usr/lib/go/pkg/linux_amd64/runtime/internal/math.a
|
|
|
packagefile runtime/internal/sys=/usr/lib/go/pkg/linux_amd64/runtime/internal/sys.a
|
|
|
EOF
|
|
|
mkdir -p $WORK/b001/exe/
|
|
|
cd .
|
|
|
/usr/lib/go/pkg/tool/linux_amd64/link -o $WORK/b001/exe/a.out -importcfg $WORK/b001/importcfg.link -buildmode=exe -buildid=FoylCipvV-SPkhyi2PJs/aJhlsTb17ElgWQeF76b5/aJhlsTb17ElgWQeF76b5/FoylCipvV-SPkhyi2PJs -extld=gcc $WORK/b001/_pkg_.a
|
|
|
/usr/lib/go/pkg/tool/linux_amd64/buildid -w $WORK/b001/exe/a.out # internal
|
|
|
mv $WORK/b001/exe/a.out empty_string
|
|
|
</code></pre><p>从上面命令输出的内容可以看到:</p>
|
|
|
<ol>
|
|
|
<li>
|
|
|
<p>Go编译器首先会创建一个任务输出临时目录(mkdir -p $WORK/b001/)。每次构建都是由一系列task完成,它们构成
|
|
|
<a href="https://github.com/golang/go/blob/master/src/cmd/go/internal/work/action.go">action graph</a>,b001是root task的工作目录。</p>
|
|
|
</li>
|
|
|
<li>
|
|
|
<p>接着将empty_string.go中依赖的包(packagefile runtime=/usr/lib/go/pkg/linux_amd64/runtime.a)写入到importcfg中</p>
|
|
|
</li>
|
|
|
<li>
|
|
|
<p>接着会使用compile命令,并指定importcfg文件,将主程序empty_string.go编译成_pkg.a文件(/usr/lib/go/pkg/tool/linux_amd64/compile -o $WORK/b001/<em>pkg</em>.a -trimpath “$WORK/b001=>” -p main -complete -buildid aJhlsTb17ElgWQeF76b5/aJhlsTb17ElgWQeF76b5 -goversion go1.14.13 -D _/home/vagrant/dive-into-go -importcfg $WORK/b001/importcfg -pack ./empty_string.go)。</p>
|
|
|
</li>
|
|
|
<li>
|
|
|
<p>程序依赖的包都写到importcfg.link这个文件中,Go编译器连接阶段中链接器会使用该文件,找到所有依赖的包文件,将其连接到程序中(/usr/lib/go/pkg/tool/linux_amd64/link -o $WORK/b001/exe/a.out -importcfg $WORK/b001/importcfg.link -buildmode=exe -buildid=FoylCipvV-SPkhyi2PJs/aJhlsTb17ElgWQeF76b5/aJhlsTb17ElgWQeF76b5/FoylCipvV-SPkhyi2PJs -extld=gcc $WORK/b001/<em>pkg</em>.a
|
|
|
/usr/lib/go/pkg/tool/linux_amd64/buildid -w $WORK/b001/exe/a.out # internal)。</p>
|
|
|
</li>
|
|
|
<li>
|
|
|
<p>将编译成功的二进制文件移动到输出目录中(mv $WORK/b001/exe/a.out empty_string)。</p>
|
|
|
</li>
|
|
|
</ol>
|
|
|
<p>为了详细查看<code>go build</code>整个详细过程,我们可以使用<code>go build -work -a -p 1 -x empty_string.go</code>命令来观察整个过程,它比<code>go build -n</code>提供了更详细的信息:</p>
|
|
|
<ul>
|
|
|
<li>-work选项指示编译器编译完成后保留编译临时工作目录</li>
|
|
|
<li>-a选项强制编译所有包。我们使用<code>go build -n</code>时候,只看到main包编译过程,这是因为其他包已经编译过了,不会再编译。我们可以使用这个选项强制编译所有包。</li>
|
|
|
<li>-p选项用来指定编译过程中线程数,这里指定为1,是为观察编译的顺序性</li>
|
|
|
<li>-x选项可以指定编译参数</li>
|
|
|
</ul>
|
|
|
<p>输出内容摘要如下:</p>
|
|
|
<pre tabindex="0"><code>vagrant@vagrant:~/dive-into-go$ go build -work -a -p 1 -x empty_string.go
|
|
|
WORK=/tmp/go-build871888098
|
|
|
mkdir -p $WORK/b004/
|
|
|
cat >$WORK/b004/go_asm.h << 'EOF' # internal
|
|
|
EOF
|
|
|
cd /usr/lib/go/src/internal/cpu
|
|
|
/usr/lib/go/pkg/tool/linux_amd64/asm -trimpath "$WORK/b004=>" -I $WORK/b004/ -I /usr/lib/go/pkg/include -D GOOS_linux -D GOARCH_amd64 -gensymabis -o $WORK/b004/symabis ./cpu_x86.s
|
|
|
cat >$WORK/b004/importcfg << 'EOF' # internal
|
|
|
# import config
|
|
|
EOF
|
|
|
/usr/lib/go/pkg/tool/linux_amd64/compile -o $WORK/b004/_pkg_.a -trimpath "$WORK/b004=>" -p internal/cpu -std -+ -buildid 8F_1bll3rU7d1mo74DFt/8F_1bll3rU7d1mo74DFt -goversion go1.14.13 -symabis $WORK/b004/symabis -D "" -importcfg $WORK/b004/importcfg -pack -asmhdr $WORK/b004/go_asm.h ./cpu.go ./cpu_amd64.go ./cpu_x86.go
|
|
|
/usr/lib/go/pkg/tool/linux_amd64/asm -trimpath "$WORK/b004=>" -I $WORK/b004/ -I /usr/lib/go/pkg/include -D GOOS_linux -D GOARCH_amd64 -o $WORK/b004/cpu_x86.o ./cpu_x86.s
|
|
|
/usr/lib/go/pkg/tool/linux_amd64/pack r $WORK/b004/_pkg_.a $WORK/b004/cpu_x86.o # internal
|
|
|
/usr/lib/go/pkg/tool/linux_amd64/buildid -w $WORK/b004/_pkg_.a # internal
|
|
|
cp $WORK/b004/_pkg_.a /home/vagrant/.cache/go-build/e2/e20b6a590621cff911735ea491492b992b429df9b0b579155aecbfdffdf7ec74-d # internal
|
|
|
mkdir -p $WORK/b003/
|
|
|
cat >$WORK/b003/go_asm.h << 'EOF' # internal
|
|
|
EOF
|
|
|
cd /usr/lib/go/src/internal/bytealg
|
|
|
/usr/lib/go/pkg/tool/linux_amd64/asm -trimpath "$WORK/b003=>" -I $WORK/b003/ -I /usr/lib/go/pkg/include -D GOOS_linux -D GOARCH_amd64 -gensymabis -o $WORK/b003/symabis ./compare_amd64.s ./count_amd64.s ./equal_amd64.s ./index_amd64.s ./indexbyte_amd64.s
|
|
|
cat >$WORK/b003/importcfg << 'EOF' # internal
|
|
|
# import config
|
|
|
packagefile internal/cpu=$WORK/b004/_pkg_.a
|
|
|
EOF
|
|
|
/usr/lib/go/pkg/tool/linux_amd64/compile -o $WORK/b003/_pkg_.a -trimpath "$WORK/b003=>" -p internal/bytealg -std -+ -buildid I0-Z7SEGCaTIz2BZXZCm/I0-Z7SEGCaTIz2BZXZCm -goversion go1.14.13 -symabis $WORK/b003/symabis -D "" -importcfg $WORK/b003/importcfg -pack -asmhdr $WORK/b003/go_asm.h ./bytealg.go ./compare_native.go ./count_native.go ./equal_generic.go ./equal_native.go ./index_amd64.go ./index_native.go ./indexbyte_native.go
|
|
|
/usr/lib/go/pkg/tool/linux_amd64/asm -trimpath "$WORK/b003=>" -I $WORK/b003/ -I /usr/lib/go/pkg/include -D GOOS_linux -D GOARCH_amd64 -o $WORK/b003/compare_amd64.o ./compare_amd64.s
|
|
|
/usr/lib/go/pkg/tool/linux_amd64/asm -trimpath "$WORK/b003=>" -I $WORK/b003/ -I /usr/lib/go/pkg/include -D GOOS_linux -D GOARCH_amd64 -o $WORK/b003/count_amd64.o ./count_amd64.s
|
|
|
/usr/lib/go/pkg/tool/linux_amd64/asm -trimpath "$WORK/b003=>" -I $WORK/b003/ -I /usr/lib/go/pkg/include -D GOOS_linux -D GOARCH_amd64 -o $WORK/b003/equal_amd64.o ./equal_amd64.s
|
|
|
/usr/lib/go/pkg/tool/linux_amd64/asm -trimpath "$WORK/b003=>" -I $WORK/b003/ -I /usr/lib/go/pkg/include -D GOOS_linux -D GOARCH_amd64 -o $WORK/b003/index_amd64.o ./index_amd64.s
|
|
|
/usr/lib/go/pkg/tool/linux_amd64/asm -trimpath "$WORK/b003=>" -I $WORK/b003/ -I /usr/lib/go/pkg/include -D GOOS_linux -D GOARCH_amd64 -o $WORK/b003/indexbyte_amd64.o ./indexbyte_amd64.s
|
|
|
/usr/lib/go/pkg/tool/linux_amd64/pack r $WORK/b003/_pkg_.a $WORK/b003/compare_amd64.o $WORK/b003/count_amd64.o $WORK/b003/equal_amd64.o $WORK/b003/index_amd64.o $WORK/b003/indexbyte_amd64.o # internal
|
|
|
/usr/lib/go/pkg/tool/linux_amd64/buildid -w $WORK/b003/_pkg_.a # internal
|
|
|
cp $WORK/b003/_pkg_.a /home/vagrant/.cache/go-build/42/42c362e050cb454a893b15620b72fbb75879ac0a1fdd13762323eec247798a43-d # internal
|
|
|
mkdir -p $WORK/b006/
|
|
|
cat >$WORK/b006/go_asm.h << 'EOF' # internal
|
|
|
EOF
|
|
|
cd /usr/lib/go/src/runtime/internal/atomic
|
|
|
/usr/lib/go/pkg/tool/linux_amd64/asm -trimpath "$WORK/b006=>" -I $WORK/b006/ -I /usr/lib/go/pkg/include -D GOOS_linux -D GOARCH_amd64 -gensymabis -o $WORK/b006/symabis ./asm_amd64.s
|
|
|
cat >$WORK/b006/importcfg << 'EOF' # internal
|
|
|
# import config
|
|
|
EOF
|
|
|
/usr/lib/go/pkg/tool/linux_amd64/compile -o $WORK/b006/_pkg_.a -trimpath "$WORK/b006=>" -p runtime/internal/atomic -std -+ -buildid uI0THQvFtr7yRsGPOXDw/uI0THQvFtr7yRsGPOXDw -goversion go1.14.13 -symabis $WORK/b006/symabis -D "" -importcfg $WORK/b006/importcfg -pack -asmhdr $WORK/b006/go_asm.h ./atomic_amd64.go ./stubs.go
|
|
|
/usr/lib/go/pkg/tool/linux_amd64/asm -trimpath "$WORK/b006=>" -I $WORK/b006/ -I /usr/lib/go/pkg/include -D GOOS_linux -D GOARCH_amd64 -o $WORK/b006/asm_amd64.o ./asm_amd64.s
|
|
|
/usr/lib/go/pkg/tool/linux_amd64/pack r $WORK/b006/_pkg_.a $WORK/b006/asm_amd64.o # internal
|
|
|
/usr/lib/go/pkg/tool/linux_amd64/buildid -w $WORK/b006/_pkg_.a # internal
|
|
|
cp $WORK/b006/_pkg_.a /home/vagrant/.cache/go-build/6b/6b2c5449e17d9b0e34bfe37a77dc16b9675ffb657fbe9277a1067fa8ca5179ab-d # internal
|
|
|
mkdir -p $WORK/b008/
|
|
|
cat >$WORK/b008/importcfg << 'EOF' # internal
|
|
|
# import config
|
|
|
EOF
|
|
|
cd /usr/lib/go/src/runtime/internal/sys
|
|
|
/usr/lib/go/pkg/tool/linux_amd64/compile -o $WORK/b008/_pkg_.a -trimpath "$WORK/b008=>" -p runtime/internal/sys -std -+ -complete -buildid AZJ761JYi_ToDiYI_5UA/AZJ761JYi_ToDiYI_5UA -goversion go1.14.13 -D "" -importcfg $WORK/b008/importcfg -pack ./arch.go ./arch_amd64.go ./intrinsics.go ./intrinsics_common.go ./stubs.go ./sys.go ./zgoarch_amd64.go ./zgoos_linux.go ./zversion.go
|
|
|
/usr/lib/go/pkg/tool/linux_amd64/buildid -w $WORK/b008/_pkg_.a # internal
|
|
|
cp $WORK/b008/_pkg_.a /home/vagrant/.cache/go-build/f7/f706a1321f01a45857a441e80fd50709a700a9d304543d534a953827021222c1-d # internal
|
|
|
mkdir -p $WORK/b007/
|
|
|
cat >$WORK/b007/importcfg << 'EOF' # internal
|
|
|
# import config
|
|
|
packagefile runtime/internal/sys=$WORK/b008/_pkg_.a
|
|
|
EOF
|
|
|
cd /usr/lib/go/src/runtime/internal/math
|
|
|
/usr/lib/go/pkg/tool/linux_amd64/compile -o $WORK/b007/_pkg_.a -trimpath "$WORK/b007=>" -p runtime/internal/math -std -+ -complete -buildid NxqylyDav-hCzDju1Kr1/NxqylyDav-hCzDju1Kr1 -goversion go1.14.13 -D "" -importcfg $WORK/b007/importcfg -pack ./math.go
|
|
|
/usr/lib/go/pkg/tool/linux_amd64/buildid -w $WORK/b007/_pkg_.a # internal
|
|
|
cp $WORK/b007/_pkg_.a /home/vagrant/.cache/go-build/f6/f6dcba7ea64d64182a26bcda498c1888786213b0b5560d9bde92cfff323be7df-d # internal
|
|
|
...
|
|
|
</code></pre><p>从上面可以看到编译器工作目录是<code>/tmp/go-build871888098</code>,cd进去之后,我们可以看到多个子目录,每个子目录都是用编译子task使用,存放的都是编译后的包:</p>
|
|
|
<pre tabindex="0"><code>vagrant@vagrant:/tmp/go-build871888098$ ls
|
|
|
b001 b002 b003 b004 b006 b007 b008
|
|
|
</code></pre><p>其中<code>b001</code>目录用于main包编译,是任务图的root节点。<code>b001</code>目录下面的<code>importcfg.link</code>文件存放都是程序所有依赖的包地址,它们指向的都是b002,b003…这些目录下的<code>_pkg_.a</code>文件。</p>
|
|
|
<h2 id="go-编译器">
|
|
|
Go 编译器
|
|
|
<a class="anchor" href="#go-%e7%bc%96%e8%af%91%e5%99%a8">#</a>
|
|
|
</h2>
|
|
|
<p>Go 编译器,英文名称是<code>Go compiler</code>,简称gc。gc是Go命令的一部分,包含在每次Go发行版本中。Go命令是由Go语言编写的,而Go 语言编写的程序需要Go命令来编译,也就是自己编译自己,这就出现了“先有鸡还是先有蛋”的问题。Go gc如何做到自己编译自己呢,要解答这个问题,我们先来了解下自举概念。</p>
|
|
|
<h3 id="自举">
|
|
|
自举
|
|
|
<a class="anchor" href="#%e8%87%aa%e4%b8%be">#</a>
|
|
|
</h3>
|
|
|
<p>自举,英文名称是Bootstrapping。自举指的是用要编译的程序的编程语言来编写其编译器。自举步骤一般如下,假定要编译的程序语言是A:</p>
|
|
|
<ol>
|
|
|
<li>先使用程序语言B实现A的编译器,假定为compiler0</li>
|
|
|
<li>接着使用A语言实现A的编译器,之后使用步骤1中的compiler0编译器编译,得到编译器compiler1</li>
|
|
|
<li>最后我们就可以使用compiler1来编译A语言写的程序,这样实现了自己编译自己</li>
|
|
|
</ol>
|
|
|
<p>通过自举方式,解决了上面说的“先有鸡还是先有蛋”的问题,实现了自己编译自己。Go语言最开始是使用C语言实现的编译器,go1.4是最后一个C语言实现的编译器版本。自go1.5开始,Go实现了自举功能,go1.5的gc是由go语言实现的,它是由go1.4版本的C语言实现编译器编译出来的,详细内容可以参见Go 自举的设计文档:
|
|
|
<a href="https://docs.google.com/document/d/1P3BLR31VA8cvLJLfMibSuTdwTuF7WWLux71CYD0eeD8/edit">Go 1.3+ Compiler Overhaul</a>。</p>
|
|
|
<p>除了 Go 语言实现的 gc 外,Go 官方还维护了一个基于 gcc 实现的 Go 编译器
|
|
|
<a href="https://go.dev/doc/install/gccgo">gccgo</a>。与 gc 相比,gccgo 编译代码较慢,但支持更强大的优化,因此由 gccgo 构建的 CPU 密集型(CPU-bound)程序通常会运行得更快。此外 gccgo 比 gc 支持更多的操作系统,如果交叉编译gc不支持的操作系统,可以考虑使用gccgo。</p>
|
|
|
<h3 id="源码安装">
|
|
|
源码安装
|
|
|
<a class="anchor" href="#%e6%ba%90%e7%a0%81%e5%ae%89%e8%a3%85">#</a>
|
|
|
</h3>
|
|
|
<p>Go 源码安装需要系统先有一个<code>bootstrap toolchain</code>,该toolchain可以从下面三种方式获取:</p>
|
|
|
<ul>
|
|
|
<li>从官网下载Go二进制发行包</li>
|
|
|
<li>使用gccgo工具编译</li>
|
|
|
<li>基于Go1.4版本的工具链</li>
|
|
|
</ul>
|
|
|
<h4 id="从官网下载发行包">
|
|
|
从官网下载发行包
|
|
|
<a class="anchor" href="#%e4%bb%8e%e5%ae%98%e7%bd%91%e4%b8%8b%e8%bd%bd%e5%8f%91%e8%a1%8c%e5%8c%85">#</a>
|
|
|
</h4>
|
|
|
<p>第一种方式是从Go发行包中获取Go二进制应用,比如要源码编译go1.14.13,我们可以去
|
|
|
<a href="https://golang.org/dl/">官网</a>下载已经编译好的go1.13,设置好GOROOT_BOOTSTRAP环境变量,就可以源码编译了。</p>
|
|
|
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>wget https://golang.org/dl/go1.13.15.linux-amd64.tar.gz
|
|
|
</span></span><span style="display:flex;"><span>tar xzvf go1.13.15.linux-amd64.tar.gz
|
|
|
</span></span><span style="display:flex;"><span>mv go go1.13.15
|
|
|
</span></span><span style="display:flex;"><span>export GOROOT_BOOTSTRAP<span style="color:#f92672">=</span>/tmp/go1.13.15 <span style="color:#75715e"># 设置GOROOT_BOOTSTRAP环境变量指向bootstrap toolchain的目录</span>
|
|
|
</span></span><span style="display:flex;"><span>
|
|
|
</span></span><span style="display:flex;"><span>cd /tmp
|
|
|
</span></span><span style="display:flex;"><span>git clone -b go1.14.13 https://go.googlesource.com/go go1.14.13
|
|
|
</span></span><span style="display:flex;"><span>cd go1.14.13/src
|
|
|
</span></span><span style="display:flex;"><span>./make.bash
|
|
|
</span></span></code></pre></div><h4 id="使用gccgo工具编译">
|
|
|
使用gccgo工具编译
|
|
|
<a class="anchor" href="#%e4%bd%bf%e7%94%a8gccgo%e5%b7%a5%e5%85%b7%e7%bc%96%e8%af%91">#</a>
|
|
|
</h4>
|
|
|
<p>第二种方式是使用gccgo来编译:</p>
|
|
|
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo apt-get install gccgo-5
|
|
|
</span></span><span style="display:flex;"><span>sudo update-alternatives --set go /usr/bin/go-5
|
|
|
</span></span><span style="display:flex;"><span>export GOROOT_BOOTSTRAP<span style="color:#f92672">=</span>/usr
|
|
|
</span></span><span style="display:flex;"><span>
|
|
|
</span></span><span style="display:flex;"><span>cd /tmp
|
|
|
</span></span><span style="display:flex;"><span>git clone -b go1.14.13 https://go.googlesource.com/go go1.14.13
|
|
|
</span></span><span style="display:flex;"><span>cd go1.14.13/src
|
|
|
</span></span><span style="display:flex;"><span>./make.bash
|
|
|
</span></span></code></pre></div><h3 id="基于go114版本工具链编译">
|
|
|
基于go1.14版本工具链编译
|
|
|
<a class="anchor" href="#%e5%9f%ba%e4%ba%8ego114%e7%89%88%e6%9c%ac%e5%b7%a5%e5%85%b7%e9%93%be%e7%bc%96%e8%af%91">#</a>
|
|
|
</h3>
|
|
|
<p>第三种方式是先编译出go1.4版本,然后使用go1.4版本去编译其他版本。</p>
|
|
|
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>cd /tmp
|
|
|
</span></span><span style="display:flex;"><span>git clone -b go1.4.3 https://go.googlesource.com/go go1.4
|
|
|
</span></span><span style="display:flex;"><span>cd go1.4/src
|
|
|
</span></span><span style="display:flex;"><span>./all.bash <span style="color:#75715e"># go1.4版本是c语言实现的编译器</span>
|
|
|
</span></span><span style="display:flex;"><span>export GOROOT_BOOTSTRAP<span style="color:#f92672">=</span>/tmp/go1.4
|
|
|
</span></span><span style="display:flex;"><span>
|
|
|
</span></span><span style="display:flex;"><span>
|
|
|
</span></span><span style="display:flex;"><span>git clone -b go1.14.13 https://go.googlesource.com/go go1.14.13
|
|
|
</span></span><span style="display:flex;"><span>cd go1.14.13/src
|
|
|
</span></span><span style="display:flex;"><span>./all.bash
|
|
|
</span></span></code></pre></div><h2 id="进一步阅读">
|
|
|
进一步阅读
|
|
|
<a class="anchor" href="#%e8%bf%9b%e4%b8%80%e6%ad%a5%e9%98%85%e8%af%bb">#</a>
|
|
|
</h2>
|
|
|
<ul>
|
|
|
<li>
|
|
|
<a href="https://medium.com/a-journey-with-go/go-overview-of-the-compiler-4e5a153ca889">Go: Overview of the Compiler</a></li>
|
|
|
<li>
|
|
|
<a href="https://getstream.io/blog/how-a-go-program-compiles-down-to-machine-code/">How a Go Program Compiles down to Machine Code</a></li>
|
|
|
<li>
|
|
|
<a href="https://draveness.me/golang/docs/part1-prerequisite/ch02-compile/golang-compile-intro/">编译原理</a></li>
|
|
|
<li>
|
|
|
<a href="http://www.en.w3ki.com/compiler_design/compiler_design_phases_of_compiler.html">Compiler appearance - Phases of Compiler</a></li>
|
|
|
<li>
|
|
|
<a href="https://github.com/golang/go/tree/master/src/cmd/compile">Introduction to the Go compiler</a></li>
|
|
|
<li>
|
|
|
<a href="https://maori.geek.nz/how-go-build-works-750bb2ba6d8e">How “go build” Works</a></li>
|
|
|
<li>
|
|
|
<a href="https://docs.google.com/document/d/1P3BLR31VA8cvLJLfMibSuTdwTuF7WWLux71CYD0eeD8/edit">Go 1.3+ Compiler Overhaul</a></li>
|
|
|
<li>
|
|
|
<a href="https://golang.org/doc/install/source">Installing Go from source</a></li>
|
|
|
<li>
|
|
|
<a href="https://github.com/golang/go/wiki/GcToolchainTricks">GcToolchainTricks</a></li>
|
|
|
<li>
|
|
|
<a href="https://weeraman.com/bootstrapping-go-ee5633ce3329">Bootstrapping Go</a></li>
|
|
|
<li>
|
|
|
<a href="https://go.dev/blog/gccgo-in-gcc-471">Gccgo in GCC 4.7.1</a></li>
|
|
|
</ul>
|
|
|
</article>
|
|
|
|
|
|
|
|
|
|
|
|
<footer class="book-footer">
|
|
|
|
|
|
<div class="flex flex-wrap justify-between">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<script>(function(){function e(e){const t=window.getSelection(),n=document.createRange();n.selectNodeContents(e),t.removeAllRanges(),t.addRange(n)}document.querySelectorAll("pre code").forEach(t=>{t.addEventListener("click",function(){if(window.getSelection().toString())return;e(t.parentElement),navigator.clipboard&&navigator.clipboard.writeText(t.parentElement.textContent)})})})()</script>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
</footer>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="book-comments">
|
|
|
<div id="disqus_thread"></div>
|
|
|
<script type="application/javascript">
|
|
|
window.disqus_config = function () {
|
|
|
|
|
|
|
|
|
|
|
|
};
|
|
|
(function() {
|
|
|
if (["localhost", "127.0.0.1"].indexOf(window.location.hostname) != -1) {
|
|
|
document.getElementById('disqus_thread').innerHTML = 'Disqus comments not available by default when the website is previewed locally.';
|
|
|
return;
|
|
|
}
|
|
|
var d = document, s = d.createElement('script'); s.async = true;
|
|
|
s.src = '//' + "go-cyub-vip" + '.disqus.com/embed.js';
|
|
|
s.setAttribute('data-timestamp', +new Date());
|
|
|
(d.head || d.body).appendChild(s);
|
|
|
})();
|
|
|
</script>
|
|
|
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
|
|
|
<a href="https://disqus.com" class="dsq-brlink">comments powered by <span class="logo-disqus">Disqus</span></a>
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<label for="menu-control" class="hidden book-menu-overlay"></label>
|
|
|
</div>
|
|
|
|
|
|
|
|
|
</main>
|
|
|
|
|
|
|
|
|
</body>
|
|
|
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|