@@ -421,7 +421,7 @@ <h2 id="index-_1">前言</h2>
421
421
<blockquote>
422
422
<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>
423
423
</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>
425
425
<p><a href="https://parallel101.github.io/cppguidebook">在 GitHub Pages 浏览本书</a> | <a href="https://142857.red/book">在小彭老师自己维护的镜像上浏览本书</a></p>
426
426
<h2 id="index-_2">格式约定</h2>
427
427
<blockquote>
@@ -6062,7 +6062,7 @@ <h2 id="type_rich_api-_47">—</h2>
6062
6062
<p>标准库的 chrono 模块就大量运用了这种强类型封装:</p>
6063
6063
<pre><code class="language-cpp">this_thread::sleep_for(chrono::seconds(3));
6064
6064
</code></pre>
6065
- <p>如果你 <code>using namespace std::literials ;</code> 还可以这样快捷地创建字面量:</p>
6065
+ <p>如果你 <code>using namespace std::literals ;</code> 还可以这样快捷地创建字面量:</p>
6066
6066
<pre><code class="language-cpp">this_thread::sleep_for(3ms); // 3 毫秒
6067
6067
this_thread::sleep_for(3s); // 3 秒
6068
6068
this_thread::sleep_for(3m); // 3 分钟
@@ -18651,9 +18651,9 @@ <h4 id="unicode-fromtolocal8bitsutf8latin1ascii">from/toLocal8Bits/Utf8/Latin1/A
18651
18651
QString str = codec->toUnicode(bytes);
18652
18652
</code></pre>
18653
18653
<h4 id="unicode-_21">字符串常量</h4>
18654
- <pre><code class="language-cpp">QString str = QStringLiterial ("你好,世界");
18654
+ <pre><code class="language-cpp">QString str = QStringLiteral ("你好,世界");
18655
18655
</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>
18657
18657
<h4 id="unicode-qtextstream">QTextStream</h4>
18658
18658
<p><code>QTextStream</code> 是 Qt 提供的文本流类(带有缓冲),它可以将文本写入到文件、套接字、标准输出等设备。</p>
18659
18659
<p>文本流和二进制流不同,他需要指定一个编码格式,通过 <code>QTextStream</code> 构造函数的第二个参数指定。</p>
@@ -20097,7 +20097,7 @@ <h2 id="llvm_intro-_8">编译器的前、中、后端</h2>
20097
20097
<p>接下来,让我们走进 LLVM 这座开源工厂,一步步观察一段 C++ 代码被编译成汇编的全过程。</p>
20098
20098
<h2 id="llvm_intro-ast">语法树(AST)</h2>
20099
20099
<p>编译器的前端负责解析 C++ 这类高级语言的源代码,生成抽象语法树(Abstract Syntax Tree,AST)。AST 是源代码的一种抽象表示,其中每个节点代表源代码中的一个语法结构,例如 if、while、for、函数调用、运算符、变量声明等。每个 AST 节点都有自己的属性,例如类型、作用域、修饰符等。</p>
20100
- <p>不同类型的 AST 节点有不同的类型名,例如 IntegerLiterial 就表示这是一个整数类型的常量,而 BinaryOperator 就表示这是一个二元运算符(可能是加减乘除等二元运算)。</p>
20100
+ <p>不同类型的 AST 节点有不同的类型名,例如 IntegerLiteral 就表示这是一个整数类型的常量,而 BinaryOperator 就表示这是一个二元运算符(可能是加减乘除等二元运算)。</p>
20101
20101
<p>AST 节点可以有一个或多个子节点,许多节点就构成了一颗语法树。每个 .cpp 文件都可以解析得到一颗语法树,在 C++ 的语法中,每颗树的根部总是一个 TranslationUnitDecl 类型的节点。这是整个翻译单元(TU)的声明,其中包含了任意多的变量、函数、类型的声明等,作为 TU 的子节点存在其中。</p>
20102
20102
<p>对树做了一些语法语义上的正确性检测后,就会遍历这颗树,为每个节点逐一生成对应的 LLVM IR,输入到中后端优化并生成真正的汇编。</p>
20103
20103
<pre><code class="language-cpp">// Clang 源码中的 AST 节点类型大致长这样(已简化)
@@ -20162,10 +20162,10 @@ <h2 id="llvm_intro-ast">语法树(AST)</h2>
20162
20162
</ul>
20163
20163
<p>接下来可以看到 CompountStmt 内部,又有两个子节点:CallExpr 和 ReturnStmt,分别是我们对 printf 函数的调用,和 <code>return 0</code> 这两条子语句。</p>
20164
20164
<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>
20166
20166
</ul>
20167
20167
<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>
20169
20169
</blockquote>
20170
20170
<p>然后我们来看 printf 函数调用这条语句:
20171
20171
<img alt="" src="../img/clang-ast-example.png" /></p>
@@ -20178,7 +20178,7 @@ <h2 id="llvm_intro-ast">语法树(AST)</h2>
20178
20178
</ol>
20179
20179
<p>这就分别用两个子节点表示了。</p>
20180
20180
<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>
20182
20182
<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>
20183
20183
</li>
20184
20184
</ul>
0 commit comments