Skip to content

Commit 89cec24

Browse files
committed
deploy: 97c100c
1 parent ef371d6 commit 89cec24

File tree

6 files changed

+17
-17
lines changed

6 files changed

+17
-17
lines changed

index.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ <h2 id="_1">前言</h2>
292292
<blockquote>
293293
<p><img src="./img/bulb.png" height="30px" width="auto" style="margin: 0; border: none"/> 本书还在持续更新中……要追番的话,可以在 <a href="https://github.com/parallel101/cppguidebook">GitHub</a> 点一下右上角的 “Watch” 按钮,每当小彭老师提交新 commit,GitHub 会向你发送一封电子邮件,提醒你小彭老师更新了。</p>
294294
</blockquote>
295-
<p>更新时间:2024年11月26日 12:32:42 (UTC+08:00)</p>
295+
<p>更新时间:2024年11月26日 12:33:25 (UTC+08:00)</p>
296296
<p><a href="https://parallel101.github.io/cppguidebook">在 GitHub Pages 浏览本书</a> | <a href="https://142857.red/book">在小彭老师自己维护的镜像上浏览本书</a></p>
297297
<h2 id="_2">格式约定</h2>
298298
<blockquote>

llvm_intro/index.html

+4-4
Original file line numberDiff line numberDiff line change
@@ -604,7 +604,7 @@ <h2 id="_8">编译器的前、中、后端</h2>
604604
<p>接下来,让我们走进 LLVM 这座开源工厂,一步步观察一段 C++ 代码被编译成汇编的全过程。</p>
605605
<h2 id="ast">语法树(AST)</h2>
606606
<p>编译器的前端负责解析 C++ 这类高级语言的源代码,生成抽象语法树(Abstract Syntax Tree,AST)。AST 是源代码的一种抽象表示,其中每个节点代表源代码中的一个语法结构,例如 if、while、for、函数调用、运算符、变量声明等。每个 AST 节点都有自己的属性,例如类型、作用域、修饰符等。</p>
607-
<p>不同类型的 AST 节点有不同的类型名,例如 IntegerLiterial 就表示这是一个整数类型的常量,而 BinaryOperator 就表示这是一个二元运算符(可能是加减乘除等二元运算)。</p>
607+
<p>不同类型的 AST 节点有不同的类型名,例如 IntegerLiteral 就表示这是一个整数类型的常量,而 BinaryOperator 就表示这是一个二元运算符(可能是加减乘除等二元运算)。</p>
608608
<p>AST 节点可以有一个或多个子节点,许多节点就构成了一颗语法树。每个 .cpp 文件都可以解析得到一颗语法树,在 C++ 的语法中,每颗树的根部总是一个 TranslationUnitDecl 类型的节点。这是整个翻译单元(TU)的声明,其中包含了任意多的变量、函数、类型的声明等,作为 TU 的子节点存在其中。</p>
609609
<p>对树做了一些语法语义上的正确性检测后,就会遍历这颗树,为每个节点逐一生成对应的 LLVM IR,输入到中后端优化并生成真正的汇编。</p>
610610
<pre><code class="language-cpp">// Clang 源码中的 AST 节点类型大致长这样(已简化)
@@ -669,10 +669,10 @@ <h2 id="ast">语法树(AST)</h2>
669669
</ul>
670670
<p>接下来可以看到 CompountStmt 内部,又有两个子节点:CallExpr 和 ReturnStmt,分别是我们对 printf 函数的调用,和 <code>return 0</code> 这两条子语句。</p>
671671
<ul>
672-
<li>ReturnStmt 很好理解,他只有一个子节点,类型是 IntegerLiterial,表示一个整形常数,整数的类型是 int,值是 0。这种有一个子节点的 ReturnStmt 节点,就表示一个有返回值的 return 语句,整体来看也就是我们代码里写的 <code>return 0</code></li>
672+
<li>ReturnStmt 很好理解,他只有一个子节点,类型是 IntegerLiteral,表示一个整形常数,整数的类型是 int,值是 0。这种有一个子节点的 ReturnStmt 节点,就表示一个有返回值的 return 语句,整体来看也就是我们代码里写的 <code>return 0</code></li>
673673
</ul>
674674
<blockquote>
675-
<p><img src="../img/book.png" height="30px" width="auto" style="margin: 0; border: none"/> 举一反三,可以想象:如果代码里写的是 <code>return x + 1</code>,那么 ReturnStmt 的子节点就会变成运算符为 <code>+</code> 的 BinaryOperator。其又具有两个子节点:左侧是 DeclRefExpr 节点,标识符为 <code>x</code>;右侧是 IntegerLiterial 节点,值为 1。</p>
675+
<p><img src="../img/book.png" height="30px" width="auto" style="margin: 0; border: none"/> 举一反三,可以想象:如果代码里写的是 <code>return x + 1</code>,那么 ReturnStmt 的子节点就会变成运算符为 <code>+</code> 的 BinaryOperator。其又具有两个子节点:左侧是 DeclRefExpr 节点,标识符为 <code>x</code>;右侧是 IntegerLiteral 节点,值为 1。</p>
676676
</blockquote>
677677
<p>然后我们来看 printf 函数调用这条语句:
678678
<img alt="" src="../img/clang-ast-example.png" /></p>
@@ -685,7 +685,7 @@ <h2 id="ast">语法树(AST)</h2>
685685
</ol>
686686
<p>这就分别用两个子节点表示了。</p>
687687
<p>注意到这里 printf 发生了一个隐式转换 ImplicitCastExpr 后才作为 CallExpr 的第一个子节点(回答了调用哪个函数的问题),并且后面注释了说 <code>FunctionToPointerDecay</code>。也就是说,<code>printf</code> 这个标识符(DeclRefExpr)本来是一个对函数标识符的引用,还没有变成函数指针,这时候还没有完成函数的重载决议。是等到函数被 <code>()</code> 调用时,才会触发重载决议,而实现区分重载的方式,实际上就是函数引用自动隐式转换成函数指针的过程所触发的,也就是这里的 ImplicitCastExpr 隐式转换节点了。这种自动发生的隐式转换被称为“退化”(decay)。所以,函数引用无法直接调用,Clang 里一直都是需要退化成指针才调用的。</p>
688-
<p>然后,这里的函数参数是一个字符串常量,按理说一个 StringLiterial 节点就可以了,为什么还有个 ImplicitCastExpr?这里有个常见误区需要纠正:很多同学常常想当然以为字符串常量的类型是 <code>const char *</code>。实际上,字符串常量的类型是 <code>const char []</code>,是一个数组类型!数组不是指针,他们是两个完全不同的类型。之所以你会有数组是指针的错觉,是因为数组可以隐式转换为元素类型的指针。而这是“退化”规则之一,这个过程在函数参数、auto 推导的时候是自动发生的(正如上面说的函数引用会在调用时自动“退化”成函数指针一样)。</p>
688+
<p>然后,这里的函数参数是一个字符串常量,按理说一个 StringLiteral 节点就可以了,为什么还有个 ImplicitCastExpr?这里有个常见误区需要纠正:很多同学常常想当然以为字符串常量的类型是 <code>const char *</code>。实际上,字符串常量的类型是 <code>const char []</code>,是一个数组类型!数组不是指针,他们是两个完全不同的类型。之所以你会有数组是指针的错觉,是因为数组可以隐式转换为元素类型的指针。而这是“退化”规则之一,这个过程在函数参数、auto 推导的时候是自动发生的(正如上面说的函数引用会在调用时自动“退化”成函数指针一样)。</p>
689689
<p>数组能自动退化成指针,不代表数组就是指针。例如 int 可以隐式转换为 double,难道就可以说“int 就是 double”吗?同样地,不能说“数组就是指针”。字符串常量的类型,从来都是 <code>const char [N]</code>,其中 <code>N</code> 是字符串中字符的个数(包括末尾自动加上的 <code>'\0'</code> 结束符)。只不过是在传入函数参数(此处是 printf 函数的字符串参数)时,自动隐式转换为 <code>const char *</code> 了而已。正如这个 ImplicitCastExpr 后面尖括号的提示中所说,ArrayToPointerDecay,是数组类型到指针类型的自动退化,从 <code>const char [14]</code> 自动隐式转换到了 <code>const char *</code></p>
690690
</li>
691691
</ul>

print_page/index.html

+8-8
Original file line numberDiff line numberDiff line change
@@ -421,7 +421,7 @@ <h2 id="index-_1">前言</h2>
421421
<blockquote>
422422
<p><img src="../img/bulb.png" height="30px" width="auto" style="margin: 0; border: none"/> 本书还在持续更新中……要追番的话,可以在 <a href="https://github.com/parallel101/cppguidebook">GitHub</a> 点一下右上角的 “Watch” 按钮,每当小彭老师提交新 commit,GitHub 会向你发送一封电子邮件,提醒你小彭老师更新了。</p>
423423
</blockquote>
424-
<p>更新时间:2024年11月26日 12:32:42 (UTC+08:00)</p>
424+
<p>更新时间:2024年11月26日 12:33:25 (UTC+08:00)</p>
425425
<p><a href="https://parallel101.github.io/cppguidebook">在 GitHub Pages 浏览本书</a> | <a href="https://142857.red/book">在小彭老师自己维护的镜像上浏览本书</a></p>
426426
<h2 id="index-_2">格式约定</h2>
427427
<blockquote>
@@ -6062,7 +6062,7 @@ <h2 id="type_rich_api-_47">&mdash;</h2>
60626062
<p>标准库的 chrono 模块就大量运用了这种强类型封装:</p>
60636063
<pre><code class="language-cpp">this_thread::sleep_for(chrono::seconds(3));
60646064
</code></pre>
6065-
<p>如果你 <code>using namespace std::literials;</code> 还可以这样快捷地创建字面量:</p>
6065+
<p>如果你 <code>using namespace std::literals;</code> 还可以这样快捷地创建字面量:</p>
60666066
<pre><code class="language-cpp">this_thread::sleep_for(3ms); // 3 毫秒
60676067
this_thread::sleep_for(3s); // 3 秒
60686068
this_thread::sleep_for(3m); // 3 分钟
@@ -18651,9 +18651,9 @@ <h4 id="unicode-fromtolocal8bitsutf8latin1ascii">from/toLocal8Bits/Utf8/Latin1/A
1865118651
QString str = codec-&gt;toUnicode(bytes);
1865218652
</code></pre>
1865318653
<h4 id="unicode-_21">字符串常量</h4>
18654-
<pre><code class="language-cpp">QString str = QStringLiterial(&quot;你好,世界&quot;);
18654+
<pre><code class="language-cpp">QString str = QStringLiteral(&quot;你好,世界&quot;);
1865518655
</code></pre>
18656-
<p><code>QStringLiterial</code> 可以保证,转换时采用的是所谓“运行字符集”(实际应该叫字面量字符编码),也就是我们开发者电脑上的“区域设置”,是编译期确定的。而如果写 <code>QString::fromLocal8Bits("")</code> 就变成 “ANSI”,客户的“区域设置”了。这两个字符编码,比如在之前跨国 galgame 的案例中,就是不同的。</p>
18656+
<p><code>QStringLiteral</code> 可以保证,转换时采用的是所谓“运行字符集”(实际应该叫字面量字符编码),也就是我们开发者电脑上的“区域设置”,是编译期确定的。而如果写 <code>QString::fromLocal8Bits("")</code> 就变成 “ANSI”,客户的“区域设置”了。这两个字符编码,比如在之前跨国 galgame 的案例中,就是不同的。</p>
1865718657
<h4 id="unicode-qtextstream">QTextStream</h4>
1865818658
<p><code>QTextStream</code> 是 Qt 提供的文本流类(带有缓冲),它可以将文本写入到文件、套接字、标准输出等设备。</p>
1865918659
<p>文本流和二进制流不同,他需要指定一个编码格式,通过 <code>QTextStream</code> 构造函数的第二个参数指定。</p>
@@ -20097,7 +20097,7 @@ <h2 id="llvm_intro-_8">编译器的前、中、后端</h2>
2009720097
<p>接下来,让我们走进 LLVM 这座开源工厂,一步步观察一段 C++ 代码被编译成汇编的全过程。</p>
2009820098
<h2 id="llvm_intro-ast">语法树(AST)</h2>
2009920099
<p>编译器的前端负责解析 C++ 这类高级语言的源代码,生成抽象语法树(Abstract Syntax Tree,AST)。AST 是源代码的一种抽象表示,其中每个节点代表源代码中的一个语法结构,例如 if、while、for、函数调用、运算符、变量声明等。每个 AST 节点都有自己的属性,例如类型、作用域、修饰符等。</p>
20100-
<p>不同类型的 AST 节点有不同的类型名,例如 IntegerLiterial 就表示这是一个整数类型的常量,而 BinaryOperator 就表示这是一个二元运算符(可能是加减乘除等二元运算)。</p>
20100+
<p>不同类型的 AST 节点有不同的类型名,例如 IntegerLiteral 就表示这是一个整数类型的常量,而 BinaryOperator 就表示这是一个二元运算符(可能是加减乘除等二元运算)。</p>
2010120101
<p>AST 节点可以有一个或多个子节点,许多节点就构成了一颗语法树。每个 .cpp 文件都可以解析得到一颗语法树,在 C++ 的语法中,每颗树的根部总是一个 TranslationUnitDecl 类型的节点。这是整个翻译单元(TU)的声明,其中包含了任意多的变量、函数、类型的声明等,作为 TU 的子节点存在其中。</p>
2010220102
<p>对树做了一些语法语义上的正确性检测后,就会遍历这颗树,为每个节点逐一生成对应的 LLVM IR,输入到中后端优化并生成真正的汇编。</p>
2010320103
<pre><code class="language-cpp">// Clang 源码中的 AST 节点类型大致长这样(已简化)
@@ -20162,10 +20162,10 @@ <h2 id="llvm_intro-ast">语法树(AST)</h2>
2016220162
</ul>
2016320163
<p>接下来可以看到 CompountStmt 内部,又有两个子节点:CallExpr 和 ReturnStmt,分别是我们对 printf 函数的调用,和 <code>return 0</code> 这两条子语句。</p>
2016420164
<ul>
20165-
<li>ReturnStmt 很好理解,他只有一个子节点,类型是 IntegerLiterial,表示一个整形常数,整数的类型是 int,值是 0。这种有一个子节点的 ReturnStmt 节点,就表示一个有返回值的 return 语句,整体来看也就是我们代码里写的 <code>return 0</code>。</li>
20165+
<li>ReturnStmt 很好理解,他只有一个子节点,类型是 IntegerLiteral,表示一个整形常数,整数的类型是 int,值是 0。这种有一个子节点的 ReturnStmt 节点,就表示一个有返回值的 return 语句,整体来看也就是我们代码里写的 <code>return 0</code>。</li>
2016620166
</ul>
2016720167
<blockquote>
20168-
<p><img src="../img/book.png" height="30px" width="auto" style="margin: 0; border: none"/> 举一反三,可以想象:如果代码里写的是 <code>return x + 1</code>,那么 ReturnStmt 的子节点就会变成运算符为 <code>+</code> 的 BinaryOperator。其又具有两个子节点:左侧是 DeclRefExpr 节点,标识符为 <code>x</code>;右侧是 IntegerLiterial 节点,值为 1。</p>
20168+
<p><img src="../img/book.png" height="30px" width="auto" style="margin: 0; border: none"/> 举一反三,可以想象:如果代码里写的是 <code>return x + 1</code>,那么 ReturnStmt 的子节点就会变成运算符为 <code>+</code> 的 BinaryOperator。其又具有两个子节点:左侧是 DeclRefExpr 节点,标识符为 <code>x</code>;右侧是 IntegerLiteral 节点,值为 1。</p>
2016920169
</blockquote>
2017020170
<p>然后我们来看 printf 函数调用这条语句:
2017120171
<img alt="" src="../img/clang-ast-example.png" /></p>
@@ -20178,7 +20178,7 @@ <h2 id="llvm_intro-ast">语法树(AST)</h2>
2017820178
</ol>
2017920179
<p>这就分别用两个子节点表示了。</p>
2018020180
<p>注意到这里 printf 发生了一个隐式转换 ImplicitCastExpr 后才作为 CallExpr 的第一个子节点(回答了调用哪个函数的问题),并且后面注释了说 <code>FunctionToPointerDecay</code>。也就是说,<code>printf</code> 这个标识符(DeclRefExpr)本来是一个对函数标识符的引用,还没有变成函数指针,这时候还没有完成函数的重载决议。是等到函数被 <code>()</code> 调用时,才会触发重载决议,而实现区分重载的方式,实际上就是函数引用自动隐式转换成函数指针的过程所触发的,也就是这里的 ImplicitCastExpr 隐式转换节点了。这种自动发生的隐式转换被称为“退化”(decay)。所以,函数引用无法直接调用,Clang 里一直都是需要退化成指针才调用的。</p>
20181-
<p>然后,这里的函数参数是一个字符串常量,按理说一个 StringLiterial 节点就可以了,为什么还有个 ImplicitCastExpr?这里有个常见误区需要纠正:很多同学常常想当然以为字符串常量的类型是 <code>const char *</code>。实际上,字符串常量的类型是 <code>const char []</code>,是一个数组类型!数组不是指针,他们是两个完全不同的类型。之所以你会有数组是指针的错觉,是因为数组可以隐式转换为元素类型的指针。而这是“退化”规则之一,这个过程在函数参数、auto 推导的时候是自动发生的(正如上面说的函数引用会在调用时自动“退化”成函数指针一样)。</p>
20181+
<p>然后,这里的函数参数是一个字符串常量,按理说一个 StringLiteral 节点就可以了,为什么还有个 ImplicitCastExpr?这里有个常见误区需要纠正:很多同学常常想当然以为字符串常量的类型是 <code>const char *</code>。实际上,字符串常量的类型是 <code>const char []</code>,是一个数组类型!数组不是指针,他们是两个完全不同的类型。之所以你会有数组是指针的错觉,是因为数组可以隐式转换为元素类型的指针。而这是“退化”规则之一,这个过程在函数参数、auto 推导的时候是自动发生的(正如上面说的函数引用会在调用时自动“退化”成函数指针一样)。</p>
2018220182
<p>数组能自动退化成指针,不代表数组就是指针。例如 int 可以隐式转换为 double,难道就可以说“int 就是 double”吗?同样地,不能说“数组就是指针”。字符串常量的类型,从来都是 <code>const char [N]</code>,其中 <code>N</code> 是字符串中字符的个数(包括末尾自动加上的 <code>'\0'</code> 结束符)。只不过是在传入函数参数(此处是 printf 函数的字符串参数)时,自动隐式转换为 <code>const char *</code> 了而已。正如这个 ImplicitCastExpr 后面尖括号的提示中所说,ArrayToPointerDecay,是数组类型到指针类型的自动退化,从 <code>const char [14]</code> 自动隐式转换到了 <code>const char *</code>。</p>
2018320183
</li>
2018420184
</ul>

search/search_index.json

+1-1
Large diffs are not rendered by default.

type_rich_api/index.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -1544,7 +1544,7 @@ <h2 id="_47">&mdash;</h2>
15441544
<p>标准库的 chrono 模块就大量运用了这种强类型封装:</p>
15451545
<pre><code class="language-cpp">this_thread::sleep_for(chrono::seconds(3));
15461546
</code></pre>
1547-
<p>如果你 <code>using namespace std::literials;</code> 还可以这样快捷地创建字面量:</p>
1547+
<p>如果你 <code>using namespace std::literals;</code> 还可以这样快捷地创建字面量:</p>
15481548
<pre><code class="language-cpp">this_thread::sleep_for(3ms); // 3 毫秒
15491549
this_thread::sleep_for(3s); // 3 秒
15501550
this_thread::sleep_for(3m); // 3 分钟

unicode/index.html

+2-2
Original file line numberDiff line numberDiff line change
@@ -3360,9 +3360,9 @@ <h4 id="fromtolocal8bitsutf8latin1ascii">from/toLocal8Bits/Utf8/Latin1/Ascii</h4
33603360
QString str = codec-&gt;toUnicode(bytes);
33613361
</code></pre>
33623362
<h4 id="_21">字符串常量</h4>
3363-
<pre><code class="language-cpp">QString str = QStringLiterial(&quot;你好,世界&quot;);
3363+
<pre><code class="language-cpp">QString str = QStringLiteral(&quot;你好,世界&quot;);
33643364
</code></pre>
3365-
<p><code>QStringLiterial</code> 可以保证,转换时采用的是所谓“运行字符集”(实际应该叫字面量字符编码),也就是我们开发者电脑上的“区域设置”,是编译期确定的。而如果写 <code>QString::fromLocal8Bits("")</code> 就变成 “ANSI”,客户的“区域设置”了。这两个字符编码,比如在之前跨国 galgame 的案例中,就是不同的。</p>
3365+
<p><code>QStringLiteral</code> 可以保证,转换时采用的是所谓“运行字符集”(实际应该叫字面量字符编码),也就是我们开发者电脑上的“区域设置”,是编译期确定的。而如果写 <code>QString::fromLocal8Bits("")</code> 就变成 “ANSI”,客户的“区域设置”了。这两个字符编码,比如在之前跨国 galgame 的案例中,就是不同的。</p>
33663366
<h4 id="qtextstream">QTextStream</h4>
33673367
<p><code>QTextStream</code> 是 Qt 提供的文本流类(带有缓冲),它可以将文本写入到文件、套接字、标准输出等设备。</p>
33683368
<p>文本流和二进制流不同,他需要指定一个编码格式,通过 <code>QTextStream</code> 构造函数的第二个参数指定。</p>

0 commit comments

Comments
 (0)